本文閱讀的源碼為Google V8 Engine v3.29.45虱岂,此版本的promise實現(xiàn)為js版本,在后續(xù)版本Google繼續(xù)對其實現(xiàn)進(jìn)行了處理叮贩。引入了es6語法等几于,在7.X版本迭代后,逐漸迭代成了C版本實現(xiàn)筹陵。
貼上源碼地址刽锤,大家自覺傳送。
代碼中所有類似%functionName的函數(shù)均是C語言實現(xiàn)的運(yùn)行時函數(shù)朦佩。
事件循環(huán)
JavaScript的一大特點就是單線程并思,而這個線程中擁有唯一的一個事件循環(huán)。
一個線程中语稠,事件循環(huán)是唯一的宋彼,但是任務(wù)隊列可以擁有多個。
任務(wù)隊列又分為macro-task(宏任務(wù))與micro-task(微任務(wù))仙畦。
宏任務(wù)包括setTimeout, setInterval输涕,微任務(wù)包括process.nextTick, Promise, Object.observe等。
不同的任務(wù)會進(jìn)入不同的隊列慨畸,而Promise屬于微任務(wù)莱坎,會進(jìn)入微任務(wù)隊列進(jìn)行處理。
編程模型
常見的編程模型有三種:單線程同步模型寸士、多線程同步模型檐什、異步編程模型碴卧。
單線程同步模型就像一個先入先出的隊列,后一個任務(wù)必須等待前一個任務(wù)完成才能繼續(xù)乃正。多線程同步模型克服了單線程等待的問題住册,但是多線程執(zhí)行由內(nèi)核調(diào)度,線程間切換代價高烫葬,高并發(fā)環(huán)境下對CPU界弧、內(nèi)存消耗較大。Javascript的作者意識到大部分耗時來源于IO等待搭综,沒有必要為IO等待耗費(fèi)昂貴的計算資源垢箕。同步任務(wù)必須在主線程上排隊執(zhí)行,前一個任務(wù)結(jié)束兑巾,后一個任務(wù)才能執(zhí)行条获。異步任務(wù)可以被掛起進(jìn)入異步隊列等待調(diào)度。瀏覽器的異步隊列分為兩種蒋歌,宏任務(wù)隊列(Scirpt帅掘、setTimeout、setInterval)和微任務(wù)隊列(Promise堂油、process.nextTick)修档。Javascript運(yùn)行時會將同步代碼放入執(zhí)行棧,當(dāng)執(zhí)行棧為空或者同步代碼執(zhí)行完畢府框,主線程會先執(zhí)行微任務(wù)隊列里的任務(wù)吱窝,再執(zhí)行宏任務(wù),如此反復(fù)執(zhí)行迫靖,直到清空執(zhí)行棧和異步隊列院峡。
Promise解決了什么問題
Javascript是函數(shù)式編程語言,函數(shù)是一等公民系宜,異步任務(wù)被主線程掛起包裝成回調(diào)函數(shù)放入任務(wù)隊列照激。這樣的設(shè)計解決了性能問題,但是多級回調(diào)的關(guān)聯(lián)變得難以維護(hù)盹牧。Promise正是針對這個問題而誕生的俩垃。
Google V8 Promise.js實現(xiàn)
整體實現(xiàn)
Promise.js聲明$Promise構(gòu)造函數(shù),使用PromiseSet初始化Promise對象汰寓,每個Promise對象具有4個屬性吆寨。
1. Promise#value(返回值)
2. Promise#status(狀態(tài),0代表pending踩寇,+1代表resolved啄清,-1代表rejected)
3. Promise#onResolve(resolve后執(zhí)行隊列)
4. Promise#onReject(reject后執(zhí)行隊列)
Promise#raw變量可以看作Javascript中的空對象{}。
InternalArray相當(dāng)于Array函數(shù)。
global變量代表瀏覽器window對象辣卒,相當(dāng)于Javascript代碼:
var Global = (function() {
? ? try { return self.self } catch (x) {}
? ? try { return global.global } catch (x) {}
? ? return null;
})();
%AddNamedProperty宏可以將變量掛載在對象上掷贾,可以看作Javascript中代碼:
function AddNamedProperty(target, name, value) {
? ? if (!_defineProperty) {
? ? ? ? target[name] = value;
? ? ? ? return;
? ? }
? ? _defineProperty(target, name, {
? ? ? ? configurable: true,
? ? ? ? writable: true,
? ? ? ? enumerable: false,
? ? ? ? value: value
? ? });
}
%AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM)將$Promise構(gòu)造函數(shù)掛載到瀏覽器window對象上。
隨后使用InstallFunctions方法分別將defer荣茫、accept想帅、reject、all啡莉、race港准、resolve掛載在$Promise上,chain咧欣、then浅缸、catch掛載在$Promise的原型鏈上。
InstallFunctions宏相當(dāng)于Javascript代碼:
function InstallFunctions(target, attr, list) {
? ? for (var i = 0; i < list.length; i += 2)
? ? ? ? AddNamedProperty(target, list[i], list[i + 1]);
}
我們來創(chuàng)建一個Promise對象看看它長什么樣子魄咕。
> var p = new $Promise(function(){})
> Promise?{Promise#status: 0, Promise#value: undefined, Promise#onResolve: Array(0), Promise#onReject: Array(0)}
Promise的底層依賴于微任務(wù)隊列衩椒,Promise.js中的宏%EnqueueMicrotask會將異步函數(shù)掛起放入微任務(wù)隊列中等待主線程調(diào)度。當(dāng)時間片來臨時哮兰,Javascrip解釋器會依次執(zhí)行微任務(wù)隊列中的所有任務(wù)毛萌。你可以簡單地使用setTImeout來模擬宏%EnqueueMicrotask的入隊操作:
var runLater = (function() {
? ? return function(fn) { setTimeout(fn, 0); };
})();
var EnqueueMicrotask = (function() {
? ? var queue = null;
? ? function flush() {
? ? ? ? var q = queue;
? ? ? ? queue = null;
? ? ? ? for (var i = 0; i < q.length; ++i)
? ? ? ? ? ? q[i]();
? ? }
? ? return function PromiseEnqueueMicrotask(fn) {
? ? ? ? // fn must not throw
? ? ? ? if (!queue) {
? ? ? ? ? ? queue = [];
? ? ? ? ? ? runLater(flush);
? ? ? ? }
? ? ? ? queue.push(fn);
? ? };
})();
構(gòu)造函數(shù)
如果你來實現(xiàn)Promise,最容易想到的就是構(gòu)造一個函數(shù)喝滞,這個函數(shù)包含一個參數(shù)阁将,這個參數(shù)同樣是函數(shù),他接受一個resolve和一個reject右遭,最終的值通過參數(shù)函數(shù)內(nèi)部調(diào)用resolve和reject來返回做盅。源碼的內(nèi)部實現(xiàn)也差不多,構(gòu)造函數(shù)$Promise調(diào)用PromiseSet方法返回一個對象狸演,這個對象就是Promise言蛇。PromiseSet方法代碼如下:
function PromiseSet(promise, status, value, onResolve, onReject) {
? ? SET_PRIVATE(promise, promiseStatus, status);
? ? SET_PRIVATE(promise, promiseValue, value);
? ? SET_PRIVATE(promise, promiseOnResolve, onResolve);
? ? SET_PRIVATE(promise, promiseOnReject, onReject);
? ? return promise;
}
SET_PRIVATE可以看作Javascript代碼
function SET_PRIVATE(obj, prop, val) { obj[prop] = val; }
promiseStatus = "Promise#status"代表Promise的狀態(tài)僻他,初始值為0代表pending宵距,它的狀態(tài)只能改變1次,要么成功為+1吨拗,要么失敗為-1满哪。
promiseValue = "Promise#value"用來記錄Promise回調(diào)運(yùn)行結(jié)果。
promiseOnResolve = "Promise#onResolve"初始值為空數(shù)組劝篷,當(dāng)異步操作串聯(lián)前面的回調(diào)沒有resolve的時候哨鸭,promiseOnResolve用來記錄后續(xù)成功回調(diào)操作。
promiseOnReject = "Promise#onReject"類似娇妓。
此外構(gòu)造函數(shù)還在內(nèi)部調(diào)用了傳入的函數(shù)resolver像鸡,并給resolver傳入了兩個值function(x) { PromiseResolve(promise, x) }和function(r) { PromiseReject(promise, r) }),這兩個值便是我們經(jīng)常在Promise內(nèi)執(zhí)行的resolve和reject函數(shù)哈恰。好了只估,看來關(guān)鍵邏輯都在function(x) { PromiseResolve(promise, x) }和function(r) { PromiseReject(promise, r) })這兩個值上志群。
PromiseResolve和PromiseReject
說到這PromiseResolve和PromiseReject就不得不說PromiseDone,因為他們只是的PromiseDone語法糖蛔钙。
PromiseResolve = function PromiseResolve(promise, x) {
? ? PromiseDone(promise, +1, x, promiseOnResolve);
};
PromiseReject = function PromiseReject(promise, r) {
? ? PromiseDone(promise, -1, r, promiseOnReject);
};
PromiseDone
Promise的狀態(tài)只能改變一次锌云,因此PromiseDone方法首先判斷Promise狀態(tài)是否改變,如果沒有改變則調(diào)用函數(shù)PromiseEnqueue吁脱,然后調(diào)用PromiseSet函數(shù)改變了Promise的狀態(tài)和返回值桑涎。
function PromiseDone(promise, status, value, promiseQueue) {
? ? if (GET_PRIVATE(promise, promiseStatus) === 0) {
? ? ? ? PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status);
? ? ? ? PromiseSet(promise, status, value);
? ? }
}
PromiseEnqueue
前面我們說過宏%EnqueueMicrotask,PromiseEnqueue函數(shù)使用宏%EnqueueMicrotask將任務(wù)task包裝到PromiseHandle函數(shù)中壓入微任務(wù)隊列兼贡。
function PromiseEnqueue(value, tasks, status) {
? ? var id, name, instrumenting = DEBUG_IS_ACTIVE;
? ? %EnqueueMicrotask(function() {
? ? ? if (instrumenting) {
? ? ? ? %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
? ? ? }
? ? ? for (var i = 0; i < tasks.length; i += 2) {
? ? ? ? PromiseHandle(value, tasks[i], tasks[i + 1])
? ? ? }
? ? ? if (instrumenting) {
? ? ? ? %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
? ? ? }
? ? });
? ? if (instrumenting) {
? ? ? id = ++lastMicrotaskId;
? ? ? name = status > 0 ? "Promise.resolve" : "Promise.reject";
? ? ? %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
? ? }
? }
上面的代碼很多是針對調(diào)試過程的攻冷,我們可以忽略宏%DebugAsyncTaskEvent的相關(guān)代碼,上面的代碼可以簡化成Javascript代碼:
function PromiseEnqueue(value, tasks, status) {
? ? EnqueueMicrotask(function() {
? ? ? ? for (var i = 0; i < tasks.length; i += 2)
? ? ? ? ? ? PromiseHandle(value, tasks[i], tasks[i + 1]);
? ? });
}
這里我們先考慮最簡單的情況紧显,先忽略then鏈?zhǔn)秸{(diào)用讲衫,因此task應(yīng)該為空。到這里就可以理解new Promise(function(resolve, reject){resolve();})的運(yùn)行過程了孵班。
PromiseThen
下面我們進(jìn)入then鏈?zhǔn)秸{(diào)用的過程涉兽,PromiseThen方法在原型鏈上,因此new Promise(function(resolve, reject){resolve();})返回的Promise對象可以調(diào)用then方法級聯(lián)篙程。PromiseThen方法內(nèi)部主要通過PromiseChain方法實現(xiàn)枷畏。我們忽略特殊情況,x應(yīng)該為resolve返回的值虱饿,因此PromiseChain方法接收onResolve和onReject兩個函數(shù)拥诡。
PromiseThen = function PromiseThen(onResolve, onReject) {
? ? onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve : PromiseIdResolveHandler;
z? ? onReject = IS_SPEC_FUNCTION(onReject) ? onReject : PromiseIdRejectHandler;
? ? var that = this;
? ? var constructor = this.constructor;
? ? return PromiseChain.call(
? ? ? ? this,
? ? ? ? function(x) {
? ? ? ? ? ? x = PromiseCoerce(constructor, x);
? ? ? ? ? ? return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
? ? ? ? ? ? ? ? IsPromise(x) ? x.then(onResolve, onReject) :
? ? ? ? ? ? ? ? onResolve(x);
? ? ? ? },
? ? ? ? onReject);
}
PromiseCoerce
承接上面x是resolve返回的值,下面的PromiseCoerce應(yīng)該直接返回x氮发。
function PromiseCoerce(constructor, x) {
? ? if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
? ? ? ? var then;
? ? ? ? try {
? ? ? ? ? ? then = x.then;
? ? ? ? } catch(r) {
? ? ? ? ? ? return PromiseRejected.call(constructor, r);
? ? ? ? }
? ? ? ? if (IS_SPEC_FUNCTION(then)) {
? ? ? ? ? ? var deferred = PromiseDeferred.call(constructor);
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? then.call(x, deferred.resolve, deferred.reject);
? ? ? ? ? ? } catch(r) {
? ? ? ? ? ? ? ? deferred.reject(r);
? ? ? ? ? ? }
? ? ? ? ? ? return deferred.promise;
? ? ? ? }
? ? }
? ? return x;
}
PromiseChain
如果讓你來實現(xiàn)Promise的鏈?zhǔn)秸{(diào)用渴肉,能夠想到的方法或許是每個then都返回一個promise對象,這個返回的promise對象就是鏈?zhǔn)秸{(diào)用的關(guān)鍵爽冕。事實上Promise.js正是這么做的仇祭,只不過它做的更精妙一些,它封裝了一個PromiseDeferred方法颈畸。每個PromiseChain內(nèi)部都調(diào)用了PromiseDeferred獲取一個deferred對象乌奇,這個對象包含一個status為0的promise對象和改變promise狀態(tài)的resolve方法和reject方法。
function PromiseDeferred() {
? ? if (this === $Promise) {
? ? ? ? // Optimized case, avoid extra closure.
? ? ? ? var promise = PromiseInit(new $Promise(promiseRaw));
? ? ? ? return {
? ? ? ? ? ? promise: promise,
? ? ? ? ? ? resolve: function(x) { PromiseResolve(promise, x) },
? ? ? ? ? ? reject: function(r) { PromiseReject(promise, r) }
? ? ? ? };
? ? } else {
? ? ? ? var result = {};
? ? ? ? result.promise = new this(function(resolve, reject) {
? ? ? ? ? ? result.resolve = resolve;
? ? ? ? ? ? result.reject = reject;
? ? ? ? });
? ? ? ? return result;
? ? }
}
接著PromiseChain進(jìn)行了一次switch判斷眯娱,前面我們忽略了then鏈?zhǔn)秸{(diào)用礁苗,PromiseEnqueue的時候task為空。現(xiàn)在我們帶上then的鏈?zhǔn)秸{(diào)用徙缴,當(dāng)前序調(diào)用未完成试伙,執(zhí)行then的時候就匹配promiseStatus為0的情況,這時就將[onResolve, deferred]加入數(shù)組promiseOnResolve,[onReject, deferred]加入數(shù)組promiseOnReject疏叨,在執(zhí)行微任務(wù)時依次執(zhí)行吱抚。如果前序resolve調(diào)用完成,就會匹配promiseStatus為+1的情況考廉,這時就可以執(zhí)行OnResolve了秘豹,因為Promise是異步的不會立即執(zhí)行,而是加入異步隊列昌粤,因此將當(dāng)前promise的onResolve使用PromiseEnqueue方法入隊既绕。前序reject的情況類似。最后返回一個新的promise對象deferred.promise涮坐,保證then的鏈?zhǔn)秸{(diào)用凄贩。到這里鏈?zhǔn)秸{(diào)用的邏輯就走通了。
PromiseChain = function PromiseChain(onResolve, onReject) {
? ? onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
? ? onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
? ? var deferred = PromiseDeferred.call(this.constructor);
? ? switch (GET_PRIVATE(this, promiseStatus)) {
? ? ? ? case UNDEFINED:
? ? ? ? ? ? throw MakeTypeError('not_a_promise', [this]);
? ? ? ? case 0:? // Pending
? ? ? ? ? ? GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
? ? ? ? ? ? GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
? ? ? ? ? ? break;
? ? ? ? case +1:? // Resolved
? ? ? ? ? ? PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred], +1);
? ? ? ? ? ? break;
? ? ? ? case -1:? // Rejected
? ? ? ? ? ? PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred], -1);
? ? ? ? ? ? break;
? ? }
? ? // Mark this promise as having handler.
? ? SET_PRIVATE(this, promiseHasHandler, true);
? ? return deferred.promise;
l}
PromiseAll
下面我們說說Promise的all方法袱讹,Promise.all方法用于將多個 Promise 實例疲扎,包裝成一個新的 Promise 實例,當(dāng)所有實例都返回時捷雕,Promise.all才會完成椒丧,只要有一個reject,結(jié)果就會reject救巷。
function PromiseAll(values) {
? ? var deferred = PromiseDeferred.call(this);
? ? var resolutions = [];
? ? if (!IsArray(values)) {
? ? ? ? deferred.reject(MakeTypeError('invalid_argument'));
? ? ? ? return deferred.promise;
? ? }
? ? try {
? ? ? ? var count = values.length;
? ? ? ? if (count === 0) {
? ? ? ? ? ? deferred.resolve(resolutions);
? ? ? ? } else {
? ? ? ? ? ? for (var i = 0; i < values.length; ++i) {
? ? ? ? ? ? ? ? this.resolve(values[i]).then(
? ? ? ? ? ? ? ? ? ? (function() {
? ? ? ? ? ? ? ? ? ? ? ? // Nested scope to get closure over current i (and avoid .bind).
? ? ? ? ? ? ? ? ? ? ? ? var i_captured = i;
? ? ? ? ? ? ? ? ? ? ? ? return function(x) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? resolutions[i_captured] = x;
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (--count === 0) deferred.resolve(resolutions);
? ? ? ? ? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? ? ? })(),
? ? ? ? ? ? ? ? ? ? function(r) { deferred.reject(r) });
? ? ? ? ? ? }
? ? ? ? }
? ? } catch (e) {
? ? ? ? deferred.reject(e);
? ? }
? ? return deferred.promise;
}
有了前面的分析壶熏,這里就可以看出PromiseAll使用PromiseDeferred創(chuàng)建了一個deferred對象,通過count--的邏輯浦译,是否所有promise都返回棒假,如果全都成功就會resolve,一個失敗結(jié)果就會失敗精盅。PromiseAll在實際應(yīng)用中可以實現(xiàn)并發(fā)的邏輯帽哑,幾個沒有依賴關(guān)系的接口可以并發(fā)調(diào)度,減少整個接口的響應(yīng)延遲叹俏。
PromiseOne
PromiseOne的實現(xiàn)也類似妻枕,它在實際使用中可以實現(xiàn)接口響應(yīng)超時的邏輯,例如一個http接口依賴于三方接口她肯,三方接口阻塞怎么都不返回佳头,使用PromiseOne可以實現(xiàn)如果三方接口2秒內(nèi)不返回就直接給用戶返回失敗的邏輯鹰贵,避免用戶不必要的等待時間晴氨。
function PromiseOne(values) {
? ? var deferred = PromiseDeferred.call(this);
? ? if (!IsArray(values)) {
? ? ? ? deferred.reject(MakeTypeError('invalid_argument'));
? ? ? ? return deferred.promise;
? ? }
? ? try {
? ? ? ? for (var i = 0; i < values.length; ++i) {
? ? ? ? ? ? this.resolve(values[i]).then(
? ? ? ? ? ? ? ? function(x) { deferred.resolve(x) },
? ? ? ? ? ? ? ? function(r) { deferred.reject(r) });
? ? ? ? }
? ? } catch (e) {
? ? ? ? deferred.reject(e);
? ? }
? ? return deferred.promise;
}