Promise的源碼實現(完美符合Promise/A+規範)

Promise是前端面試中的高頻問題,我做爲面試官的時候,問Promise的機率超過90%,據我所知,大多數公司,都會問一些關於Promise的問題。若是你能根據PromiseA+的規範,寫出符合規範的源碼,那麼我想,對於面試中的Promise相關的問題,都可以給出比較完美的答案。前端

個人建議是,對照規範多寫幾回實現,也許第一遍的時候,是改了屢次,才能經過測試,那麼須要反覆的寫,我已經將Promise的源碼實現寫了不下七遍。面試

Promise的源碼實現

/**
 * 1. new Promise時,須要傳遞一個 executor 執行器,執行器馬上執行  * 2. executor 接受兩個參數,分別是 resolve 和 reject  * 3. promise 只能從 pending 到 rejected, 或者從 pending 到 fulfilled  * 4. promise 的狀態一旦確認,就不會再改變  * 5. promise 都有 then 方法,then 接收兩個參數,分別是 promise 成功的回調 onFulfilled,  * 和 promise 失敗的回調 onRejected  * 6. 若是調用 then 時,promise已經成功,則執行 onFulfilled,並將promise的值做爲參數傳遞進去。  * 若是promise已經失敗,那麼執行 onRejected, 並將 promise 失敗的緣由做爲參數傳遞進去。  * 若是promise的狀態是pending,須要將onFulfilled和onRejected函數存放起來,等待狀態肯定後,再依次將對應的函數執行(發佈訂閱)  * 7. then 的參數 onFulfilled 和 onRejected 能夠缺省  * 8. promise 能夠then屢次,promise 的then 方法返回一個 promise  * 9. 若是 then 返回的是一個結果,那麼就會把這個結果做爲參數,傳遞給下一個then的成功的回調(onFulfilled)  * 10. 若是 then 中拋出了異常,那麼就會把這個異常做爲參數,傳遞給下一個then的失敗的回調(onRejected)  * 11.若是 then 返回的是一個promise,那麼須要等這個promise,那麼會等這個promise執行完,promise若是成功,  * 就走下一個then的成功,若是失敗,就走下一個then的失敗  */ const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function Promise(executor) { let self = this; self.status = PENDING; self.onFulfilled = [];//成功的回調 self.onRejected = []; //失敗的回調 //PromiseA+ 2.1 function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1 } } function reject(reason) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2 } } try { executor(resolve, reject); } catch (e) { reject(e); } } Promise.prototype.then = function (onFulfilled, onRejected) { //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; let self = this; //PromiseA+ 2.2.7 let promise2 = new Promise((resolve, reject) => { if (self.status === FULFILLED) { //PromiseA+ 2.2.2 //PromiseA+ 2.2.4 --- setTimeout setTimeout(() => { try { //PromiseA+ 2.2.7.1 let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { //PromiseA+ 2.2.7.2 reject(e); } }); } else if (self.status === REJECTED) { //PromiseA+ 2.2.3 setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } else if (self.status === PENDING) { self.onFulfilled.push(() => { setTimeout(() => { try { let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); self.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }); return promise2; } function resolvePromise(promise2, x, resolve, reject) { let self = this; //PromiseA+ 2.3.1 if (promise2 === x) { reject(new TypeError('Chaining cycle')); } if (x && typeof x === 'object' || typeof x === 'function') { let used; //PromiseA+2.3.3.3.3 只能調用一次 try { let then = x.then; if (typeof then === 'function') { //PromiseA+2.3.3 then.call(x, (y) => { //PromiseA+2.3.3.1 if (used) return; used = true; resolvePromise(promise2, y, resolve, reject); }, (r) => { //PromiseA+2.3.3.2 if (used) return; used = true; reject(r); }); }else{ //PromiseA+2.3.3.4 if (used) return; used = true; resolve(x); } } catch (e) { //PromiseA+ 2.3.3.2 if (used) return; used = true; reject(e); } } else { //PromiseA+ 2.3.3.4 resolve(x); } } module.exports = Promise; 

有專門的測試腳本能夠測試所編寫的代碼是否符合PromiseA+的規範。shell

首先,在promise實現的代碼中,增長如下代碼:npm

Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; }

安裝測試腳本:數組

npm install -g promises-aplus-tests

若是當前的promise源碼的文件名爲promise.jspromise

那麼在對應的目錄執行如下命令:異步

promises-aplus-tests promise.js

promises-aplus-tests中共有872條測試用例。以上代碼,能夠完美經過全部用例。async

對上面的代碼實現作一點簡要說明(其它一些內容註釋中已經寫得很清楚):函數

  1. onFulfilled 和 onFulfilled的調用須要放在setTimeout,由於規範中表示: onFulfilled or onRejected must not be called until the execution context stack contains only platform code。使用setTimeout只是模擬異步,原生Promise並不是是這樣實現的。測試

  2. 在 resolvePromise 的函數中,爲什麼須要usedd這個flag,一樣是由於規範中明確表示: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. 所以咱們須要這樣的flag來確保只會執行一次。

  3. self.onFulfilled 和 self.onRejected 中存儲了成功的回調和失敗的回調,根據規範2.6顯示,當promise從pending態改變的時候,須要按照順序去指定then對應的回調。

PromiseA+的規範(翻譯版)

PS: 下面是我翻譯的規範,供參考

術語

  1. promise 是一個有then方法的對象或者是函數,行爲遵循本規範
  2. thenable 是一個有then方法的對象或者是函數
  3. value 是promise狀態成功時的值,包括 undefined/thenable或者是 promise
  4. exception 是一個使用throw拋出的異常值
  5. reason 是promise狀態失敗時的值

要求

2.1 Promise States

Promise 必須處於如下三個狀態之一: pending, fulfilled 或者是 rejected

2.1.1 若是promise在pending狀態
2.1.1.1 能夠變成 fulfilled 或者是 rejected
2.1.2 若是promise在fulfilled狀態
2.1.2.1 不會變成其它狀態

2.1.2.2 必須有一個value值
2.1.3 若是promise在rejected狀態
2.1.3.1 不會變成其它狀態

2.1.3.2 必須有一個promise被reject的reason

歸納便是:promise的狀態只能從pending變成fulfilled,或者從pending變成rejected.promise成功,有成功的value.promise失敗的話,有失敗的緣由

2.2 then方法

promise必須提供一個then方法,來訪問最終的結果

promise的then方法接收兩個參數

promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可選參數
2.2.1.1 onFulfilled 必須是函數類型

2.2.1.2 onRejected 必須是函數類型
2.2.2 若是 onFulfilled 是函數:
2.2.2.1 必須在promise變成 fulfilled 時,調用 onFulfilled,參數是promise的value
2.2.2.2 在promise的狀態不是 fulfilled 以前,不能調用
2.2.2.3 onFulfilled 只能被調用一次
2.2.3 若是 onRejected 是函數:
2.2.3.1 必須在promise變成 rejected 時,調用 onRejected,參數是promise的reason
2.2.3.2 在promise的狀態不是 rejected 以前,不能調用
2.2.3.3 onRejected 只能被調用一次
2.2.4 onFulfilled 和 onRejected 應該是微任務
2.2.5 onFulfilled 和 onRejected 必須做爲函數被調用
2.2.6 then方法可能被屢次調用
2.2.6.1 若是promise變成了 fulfilled態,全部的onFulfilled回調都須要按照then的順序執行
2.2.6.2 若是promise變成了 rejected態,全部的onRejected回調都須要按照then的順序執行
2.2.7 then必須返回一個promise
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 onFulfilled 或 onRejected 執行的結果爲x,調用 resolvePromise
2.2.7.2 若是 onFulfilled 或者 onRejected 執行時拋出異常e,promise2須要被reject
2.2.7.3 若是 onFulfilled 不是一個函數,promise2 以promise1的值fulfilled
2.2.7.4 若是 onRejected 不是一個函數,promise2 以promise1的reason rejected

2.3 resolvePromise

resolvePromise(promise2, x, resolve, reject)

2.3.1 若是 promise2 和 x 相等,那麼 reject promise with a TypeError
2.3.2 若是 x 是一個 promsie
2.3.2.1 若是x是pending態,那麼promise必需要在pending,直到 x 變成 fulfilled or rejected.
2.3.2.2 若是 x 被 fulfilled, fulfill promise with the same value.
2.3.2.3 若是 x 被 rejected, reject promise with the same reason.
2.3.3 若是 x 是一個 object 或者 是一個 function
2.3.3.1 let then = x.then.
2.3.3.2 若是 x.then 這步出錯,那麼 reject promise with e as the reason..
2.3.3.3 若是 then 是一個函數,then.call(x, resolvePromiseFn, rejectPromise)
    2.3.3.3.1 resolvePromiseFn 的 入參是 y, 執行 resolvePromise(promise2, y, resolve, reject);
    2.3.3.3.2 rejectPromise 的 入參是 r, reject promise with r.
    2.3.3.3.3 若是 resolvePromise 和 rejectPromise 都調用了,那麼第一個調用優先,後面的調用忽略。
    2.3.3.3.4 若是調用then拋出異常e 
        2.3.3.3.4.1 若是 resolvePromise 或 rejectPromise 已經被調用,那麼忽略
        2.3.3.3.4.3 不然,reject promise with e as the reason
2.3.3.4 若是 then 不是一個function. fulfill promise with x.
2.3.4 若是 x 不是一個 object 或者 function,fulfill promise with x.

Promise的其餘方法

雖然上述的promise源碼已經符合PromiseA+的規範,可是原生的Promise還提供了一些其餘方法,如:

  1. Promise.resolve()
  2. Promise.reject()
  3. Promise.prototype.catch()
  4. Promise.prototype.finally()
  5. Promise.all()
  6. Promise.race()

下面具體說一下每一個方法的實現:

Promise.resolve

Promise.resolve(value) 返回一個以給定值解析後的Promise 對象.

  1. 若是 value 是個 thenable 對象,返回的promise會「跟隨」這個thenable的對象,採用它的最終狀態
  2. 若是傳入的value自己就是promise對象,那麼Promise.resolve將不作任何修改、原封不動地返回這個promise對象。
  3. 其餘狀況,直接返回以該值爲成功狀態的promise對象。
Promise.resolve = function (param) { if (param instanceof Promise) { return param; } return new Promise((resolve, reject) => { if (param && param.then && typeof param.then === 'function') { setTimeout(() => { param.then(resolve, reject); }); } else { resolve(param); } }); }

thenable對象的執行加 setTimeout的緣由是根據原生Promise對象執行的結果推斷的,以下的測試代碼,原生的執行結果爲: 20 400 30;爲了一樣的執行順序,增長了setTimeout延時。

測試代碼:

let p = Promise.resolve(20); p.then((data) => { console.log(data); }); let p2 = Promise.resolve({ then: function(resolve, reject) { resolve(30); } }); p2.then((data)=> { console.log(data) }); let p3 = Promise.resolve(new Promise((resolve, reject) => { resolve(400) })); p3.then((data) => { console.log(data) });

Promise.reject

Promise.reject方法和Promise.resolve不一樣,Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。

Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); }); }

Promise.prototype.catch

Promise.prototype.catch 用於指定出錯時的回調,是特殊的then方法,catch以後,能夠繼續 .then

Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); }

Promise.prototype.finally

無論成功仍是失敗,都會走到finally中,而且finally以後,還能夠繼續then。而且會將值原封不動的傳遞給後面的then.

Promise.prototype.finally = function (callback) { return this.then((value) => { return Promise.resolve(callback()).then(() => { return value; }); }, (err) => { return Promise.resolve(callback()).then(() => { throw err; }); }); }

Promise.all

Promise.all(promises) 返回一個promise對象

  1. 若是傳入的參數是一個空的可迭代對象,那麼此promise對象回調完成(resolve),只有此狀況,是同步執行的,其它都是異步返回的。
  2. 若是傳入的參數不包含任何 promise,則返回一個異步完成.
  3. promises 中全部的promise都promise都「完成」時或參數中不包含 promise 時回調完成。
  4. 若是參數中有一個promise失敗,那麼Promise.all返回的promise對象失敗
  5. 在任何狀況下,Promise.all 返回的 promise 的完成狀態的結果都是一個數組
Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let index = 0;
        let result = [];
        if (promises.length === 0) {
            resolve(result);
        } else {
            function processValue(i, data) {
                result[i] = data;
                if (++index === promises.length) {
                    resolve(result);
                }
            }
            for (let i = 0; i < promises.length; i++) {
                    //promises[i] 多是普通值
                    Promise.resolve(promises[i]).then((data) => {
                    processValue(i, data);
                }, (err) => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

測試代碼:

var promise1 = new Promise((resolve, reject) => { resolve(3); }) var promise2 = 42; var promise3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); //[3, 42, 'foo'] },(err)=>{ console.log(err) }); var p = Promise.all([]); // will be immediately resolved var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously console.log(p); console.log(p2) setTimeout(function(){ console.log('the stack is now empty'); console.log(p2); });

Promise.race

Promise.race函數返回一個 Promise,它將與第一個傳遞的 promise 相同的完成方式被完成。它能夠是完成( resolves),也能夠是失敗(rejects),這要取決於第一個完成的方式是兩個中的哪一個。

若是傳的參數數組是空,則返回的 promise 將永遠等待。

若是迭代包含一個或多個非承諾值和/或已解決/拒絕的承諾,則 Promise.race 將解析爲迭代中找到的第一個值。

Promise.race = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { return; } else { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then((data) => { resolve(data); return; }, (err) => { reject(err); return; }); } } }); }

測試代碼:

Promise.race([ new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }), undefined, new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) }) ]).then((data) => { console.log('success ', data); }, (err) => { console.log('err ',err); }); Promise.race([ new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }), new Promise((resolve, reject) => { setTimeout(() => { resolve(200) }, 200) }), new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) }) ]).then((data) => { console.log(data); }, (err) => { console.log(err); });
相關文章
相關標籤/搜索