您好,登錄后才能下訂單哦!
if (typeof Promise === 'undefined') { return}
實現 Promise/A+ 規范的庫有很多,lie 是一個精簡的實現 Promise/A+ 的庫,并且通過了 Promise/A+ 專門的測試集,但 lie 的代碼寫的有點繞,我在 lie 的代碼基礎上進行了修改,使之更容易閱讀和理解,并發布了 appoint 模塊供大家參考。
Promise 規范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升級版 Promise/A+,有興趣的可以去了解下,最終 ES6 中采用了 Promise/A+ 規范。在講解 Promise 實現之前,當然要先了解 Promise/A+ 規范。Promise/A+ 規范參考:
英文版:https://promisesaplus.com/
中文版:http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/
注意:沒有特殊說明以下 promise 均指代 Promise 實例。
規范雖然不長,但細節也比較多,我挑出幾個要點簡單說明下:
Promise 本質是一個狀態機。每個 promise 只能是 3 種狀態中的一種:pending、fulfilled 或 rejected。狀態轉變只能是 pending -> fulfilled 或者 pending -> rejected。狀態轉變不可逆。
then 方法可以被同一個 promise 調用多次。
then 方法必須返回一個 promise。規范里沒有明確說明返回一個新的 promise 還是復用老的 promise(即 return this),大多數實現都是返回一個新的 promise,而且復用老的 promise 可能改變內部狀態,這與規范也是相違背的。
值穿透。下面會細講。
我們知道 Promise 是一個構造函數,需要用 new 調用,并有以下幾個 api:
function Promise(resolver) {}Promise.prototype.then = function() {}Promise.prototype.catch = function() {}Promise.resolve = function() {}Promise.reject = function() {}Promise.all = function() {}Promise.race = function() {}
下面我們以 appoint 為最終目標,開始一步一步構建完整的 Promise 實現。
'use strict';var immediate = require('immediate');function INTERNAL() {}function isFunction(func) { return typeof func === 'function';}function isObject(obj) { return typeof obj === 'object';}function isArray(arr) { return Object.prototype.toString.call(arr) === '[object Array]';}var PENDING = 0;var FULFILLED = 1;var REJECTED = 2;module.exports = Promise;function Promise(resolver) { if (!isFunction(resolver)) { throw new TypeError('resolver must be a function'); } this.state = PENDING; this.value = void 0; this.queue = []; if (resolver !== INTERNAL) { safelyResolveThen(this, resolver); }}
immediate 是一個將同步轉異步執行的庫。INTERNAL 就是一個空函數,類似于一些代碼庫中的 noop。定義了 3 個輔助函數:isFunction、isObject 和 isArray。定義了 3 種狀態:PENDING、FULFILLED 和 REJECTED。safelyResolveThen 后面講。promise 內部有三個變量:
state: 當前 promise 的狀態,初始值為 PENDING。狀態改變只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
value: 當 state 是 FULFILLED 時存儲返回值,當 state 是 REJECTED 時存儲錯誤。
queue: promise 內部的回調隊列,這是個什么玩意兒?為什么是一個數組?
先看一段代碼:
var Promise = require('appoint')var promise = new Promise((resolve) => { setTimeout(() => { resolve('haha') }, 1000)})var a = promise.then(function onSuccess() {})var b = promise.catch(function onError() {})console.log(require('util').inspect(promise, { depth: 10 }))console.log(promise.queue[0].promise === a)console.log(promise.queue[1].promise === b)
打印出:
Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [] }, callFulfilled: [Function], callRejected: [Function] }, QueueItem { promise: Promise { state: 0, value: undefined, queue: [] }, callFulfilled: [Function], callRejected: [Function] } ] }truetrue
可以看出,queue 數組中有兩個對象。因為規范中規定:then 方法可以被同一個 promise 調用多次。上例中在調用 .then 和 .catch 時 promise 并沒有被 resolve,所以將 .then 和 .catch 生成的新 promise(a 和 b) 和正確時的回調(onSuccess 包裝成 callFulfilled)和錯誤時的回調(onError 包裝成 callRejected)生成一個 QueueItem 實例并 push 到 queue 數組里,所以上面兩個 console.log 打印 true。當 promise 狀態改變時遍歷內部 queue 數組,統一執行成功(FULFILLED -> callFulfilled)或失敗(REJECTED -> callRejected)的回調(傳入 promise 的 value 值),生成的結果分別設置 a 和 b 的 state 和 value,這就是 Promise 實現的基本原理。
再來看另一個例子:
var Promise = require('appoint') var promise = new Promise((resolve) => { setTimeout(() => { resolve('haha') }, 1000) }) promise .then(() => {}) .then(() => {}) .then(() => {}) console.log(require('util').inspect(promise, { depth: 10 }))
打印出:
Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [] }, callFulfilled: [Function], callRejected: [Function] } ] }, callFulfilled: [Function], callRejected: [Function] } ] }, callFulfilled: [Function], callRejected: [Function] } ] }
調用了 3 次 then,每個 then 將它生成的 promise 放到了調用它的 promise 隊列里,形成了 3 層調用關系。當最外層的 promise 狀態改變時,遍歷它的 queue 數組調用對應的回調,設置子 promise 的 state 和 value 并遍歷它的 queue 數組調用對應的回調,然后設置孫 promise 的 state 和 value 并遍歷它的 queue 數組調用對應的回調......依次類推。
function safelyResolveThen(self, then) { var called = false; try { then(function (value) { if (called) { return; } called = true; doResolve(self, value); }, function (error) { if (called) { return; } called = true; doReject(self, error); }); } catch (error) { if (called) { return; } called = true; doReject(self, error); } }
safelyResolveThen 顧名思義用來『安全的執行 then 函數』,這里的 then 函數指『第一個參數是 resolve 函數第二個參數是 reject 函數的函數』,如下兩種情況:
構造函數的參數,即這里的 resolver:
new Promise(function resolver(resolve, reject) { setTimeout(() => { resolve('haha') }, 1000)})
promise 的 then:
promise.then(resolve, reject)
safelyResolveThen 有 3 個作用:
try...catch 捕獲拋出的異常,如:
new Promise(function resolver(resolve, reject) { throw new Error('Oops') })
called 控制 resolve 或 reject 只執行一次,多次調用沒有任何作用。即:
var Promise = require('appoint')var promise = new Promise(function resolver(resolve, reject) { setTimeout(() => { resolve('haha') }, 1000) reject('error')})promise.then(console.log)promise.catch(console.error)
打印 error,不會再打印 haha。
沒有錯誤則執行 doResolve,有錯誤則執行 doReject。
function doResolve(self, value) { try { var then = getThen(value); if (then) { safelyResolveThen(self, then); } else { self.state = FULFILLED; self.value = value; self.queue.forEach(function (queueItem) { queueItem.callFulfilled(value); }); } return self; } catch (error) { return doReject(self, error); }}function doReject(self, error) { self.state = REJECTED; self.value = error; self.queue.forEach(function (queueItem) { queueItem.callRejected(error); }); return self;}
doReject 就是設置 promise 的 state 為 REJECTED,value 為 error,callRejected 如前面提到的通知子 promise:『我這里出了點問題呀』然后子 promise 根據傳入的錯誤設置自己的狀態和值。doResolve 結合 safelyResolveThen 使用不斷地解包 promise,直至返回值是非 promise 對象后,設置 promise 的狀態和值,然后通知子 promise:『我這里有值了喲』然后子 promise 根據傳入的值設置自己的狀態和值。
這里有個輔助函數 getThen:
function getThen(obj) { var then = obj && obj.then; if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) { return function appyThen() { then.apply(obj, arguments); }; } }
規范中規定:如果 then 是函數,將 x(這里是 obj) 作為函數的 this 調用。
Promise.prototype.then = function (onFulfilled, onRejected) { if (!isFunction(onFulfilled) && this.state === FULFILLED || !isFunction(onRejected) && this.state === REJECTED) { return this; } var promise = new this.constructor(INTERNAL); if (this.state !== PENDING) { var resolver = this.state === FULFILLED ? onFulfilled : onRejected; unwrap(promise, resolver, this.value); } else { this.queue.push(new QueueItem(promise, onFulfilled, onRejected)); } return promise;};Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected);};
上述代碼中的 return this 實現了值穿透,后面會講。可以看出,then 方法中生成了一個新的 promise 然后返回,符合規范要求。如果 promise 的狀態改變了,則調用 unwrap,否則將生成的 promise 加入到當前 promise 的回調隊列 queue 里,之前講解了如何消費 queue。有 3 點需要講解:
Promise 構造函數傳入了一個 INTERNAL 即空函數,因為這個新產生的 promise 可以認為是內部的 promise,需要根據外部的 promise 的狀態和值產生自身的狀態和值,不需要傳入回調函數,而外部 Promise 需要傳入回調函數決定它的狀態和值。所以之前 Promise 的構造函數里做了判斷區分外部調用還是內部調用:
if (resolver !== INTERNAL) { safelyResolveThen(this, resolver);}
unwrap 代碼如下:
function unwrap(promise, func, value) { immediate(function () { var returnValue; try { returnValue = func(value); } catch (error) { return doReject(promise, error); } if (returnValue === promise) { doReject(promise, new TypeError('Cannot resolve promise with itself')); } else { doResolve(promise, returnValue); } });}
從名字也可以理解是用來解包(即執行函數)的,第一個參數是子 promise,第二個參數是父 promise 的 then 的回調(onFulfilled/onRejected),第三個參數是父 promise 的值(正常值/錯誤)。有 3 點需要說明:
使用 immediate 將同步代碼變異步。如:
var Promise = require('appoint')var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('haha') }, 1000)})promise.then(() => { promise.then(() => { console.log('1') }) console.log('2')})
打印 2 1,去掉 immediate 則打印 1 2。
try...catch 用來捕獲 then/catch 內拋出的異常,并調用 doReject,如:
promise.then(() => { throw new Error('haha')})promise.catch(() => { throw new Error('haha')})
返回的值不能是 promise 本身,否則會造成死循環,如 node@4.6.0 下運行:
var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('haha') }, 1000) }) var a = promise.then(() => { return a }) a.catch(console.log)// [TypeError: Chaining cycle detected for promise #<Promise>]
QueueItem 代碼如下:
function QueueItem(promise, onFulfilled, onRejected) { this.promise = promise; this.callFulfilled = function (value) { doResolve(this.promise, value); }; this.callRejected = function (error) { doReject(this.promise, error); }; if (isFunction(onFulfilled)) { this.callFulfilled = function (value) { unwrap(this.promise, onFulfilled, value); }; } if (isFunction(onRejected)) { this.callRejected = function (error) { unwrap(this.promise, onRejected, error); }; }}
promise 為 then 生成的新 promise(以下稱為『子promise』),onFulfilled 和 onRejected 即是 then 參數中的 onFulfilled 和 onRejected。從上面代碼可以看出:比如當 promise 狀態變為 FULFILLED 時,之前注冊的 then 函數,用 callFulfilled 調用 unwrap 進行解包最終得出子 promise 的狀態和值,之前注冊的 catch 函數,用 callFulfilled 直接調用 doResolve,設置隊列里子 promise 的狀態和值。當 promise 狀態變為 REJECTED 類似。
注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的語法糖。
至此,Promise 的核心實現都完成了。
Promise.prototype.then = function (onFulfilled, onRejected) { if (!isFunction(onFulfilled) && this.state === FULFILLED || !isFunction(onRejected) && this.state === REJECTED) { return this; } ...};
上面提到了值穿透問題,值穿透即:
var Promise = require('appoint') var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('haha') }, 1000) }) promise .then('hehe') .then(console.log)
最終打印 haha 而不是 hehe。
通過 return this 只實現了值穿透的一種情況,其實值穿透有兩種情況:
promise 已經是 FULFILLED/REJECTED 時,通過 return this 實現的值穿透:
var Promise = require('appoint')var promise = new Promise(function (resolve) { setTimeout(() => { resolve('haha') }, 1000)})promise.then(() => { promise.then().then((res) => {// ① console.log(res)// haha }) promise.catch().then((res) => {// ② console.log(res)// haha }) console.log(promise.then() === promise.catch())// true console.log(promise.then(1) === promise.catch({ name: 'nswbmw' }))// true})
上述代碼①②處 promise 已經是 FULFILLED 了符合條件所以執行了 return this。注意:原生的 Promise 實現里并不是這樣實現的,所以會打印兩個 false。
promise 是 PENDING 時,通過生成新的 promise 加入到父 promise 的 queue,父 promise 有值時調用 callFulfilled->doResolve 或 callRejected->doReject(因為 then/catch 傳入的參數不是函數)設置子 promise 的狀態和值為父 promise 的狀態和值。如:
var Promise = require('appoint')var promise = new Promise((resolve) => { setTimeout(() => { resolve('haha') }, 1000)})var a = promise.then()a.then((res) => { console.log(res)// haha})var b = promise.catch()b.then((res) => { console.log(res)// haha})console.log(a === b)// false
Promise.resolve = resolve;function resolve(value) { if (value instanceof this) { return value; } return doResolve(new this(INTERNAL), value);}Promise.reject = reject;function reject(reason) { var promise = new this(INTERNAL); return doReject(promise, reason);}
當 Promise.resolve 參數是一個 promise 時,直接返回該值。
Promise.all = all;function all(iterable) { var self = this; if (!isArray(iterable)) { return this.reject(new TypeError('must be an array')); } var len = iterable.length; var called = false; if (!len) { return this.resolve([]); } var values = new Array(len); var resolved = 0; var i = -1; var promise = new this(INTERNAL); while (++i < len) { allResolver(iterable[i], i); } return promise; function allResolver(value, i) { self.resolve(value).then(resolveFromAll, function (error) { if (!called) { called = true; doReject(promise, error); } }); function resolveFromAll(outValue) { values[i] = outValue; if (++resolved === len && !called) { called = true; doResolve(promise, values); } } }}
Promise.all 用來并行執行多個 promise/值,當所有 promise/值執行完畢后或有一個發生錯誤時返回。可以看出:
Promise.all 內部生成了一個新的 promise 返回。
called 用來控制即使有多個 promise reject 也只有第一個生效。
values 用來存儲結果。
當最后一個 promise 得出結果后,使用 doResolve(promise, values) 設置 promise 的 state 為 FULFILLED,value 為結果數組 values。
Promise.race = race;function race(iterable) { var self = this; if (!isArray(iterable)) { return this.reject(new TypeError('must be an array')); } var len = iterable.length; var called = false; if (!len) { return this.resolve([]); } var i = -1; var promise = new this(INTERNAL); while (++i < len) { resolver(iterable[i]); } return promise; function resolver(value) { self.resolve(value).then(function (response) { if (!called) { called = true; doResolve(promise, response); } }, function (error) { if (!called) { called = true; doReject(promise, error); } }); }}
Promise.race 接受一個數組,當數組中有一個 resolve 或 reject 時返回。跟 Promise.all 代碼相近,只不過這里用 called 控制只要有任何一個 promise onFulfilled/onRejected 立即去設置 promise 的狀態和值。
至此,Promise 的實現全部講解完畢。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。