GitHub
前端
Promise是前端大廠面試的一道常考題,掌握Promise用法及其相關原理,對你的面試必定有很大幫助。這篇文章主要講解Promise源碼實現,若是你尚未掌握Promise的功能和API,推薦你先去學習一下Promise的概念和使用API,學習知識就要腳踏實地,先把基礎搞好才能深入理解源碼的實現。
這裏推薦阮一峯老師的文章 git
ES6入門-Promise對象 es6
若是你已經掌握了Promise的基本用法,咱們進行下一步github
說到Promise/A+規範,不少同窗可能很不理解這是一個什麼東西,下面給出兩個地址,不瞭解的同窗須要先了解一下,對咱們後續理解源碼頗有幫助,先看兩遍,有些地方看不懂也不要緊,後續咱們能夠經過源碼來回頭再理解,想把一個知識真的學會,就要反覆琢磨,從【確定->否認->再確定】不斷地深刻理解,直到徹底掌握。 面試
Promise/A+規範英文地址
Promise/A+規範中文翻譯 算法
若是你看過了Promise/A+規範,咱們繼續,我會帶着你們按照規範要求,一步一步的來實現源碼npm
一個promise必須處於三種狀態之一: 請求態(pending), 完成態(fulfilled),拒絕態(rejected)數組
2.1.2.2 必須有一個值,且此值不能改變promise
咱們先找需求來完成這一部分代碼,一個簡單的小架子異步
// 2.1 狀態常量 const PENDING = 'pending'; const RESOLVED = 'resolved'; const REJECTED = 'rejected'; // Promise構造函數 function MyPromise(fn) { const that = this; this.state = PENDING; this.value = null; this.resolvedCallbacks = []; this.rejectedCallbacks = []; function resolve() { if (that.state === PENDING) { } } function reject() { if (that.state === PENDING) { } } }
上面這段代碼完成了Promise構造函數的初步搭建,包含:
下面咱們來完成resolve和reject
function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value)) } } function reject(value) { if (that.state === PENDING) { that.state = REJECTED that.value = value that.rejectedCallbacks.map(cb => cb(that.value)) } }
記下來咱們須要來執行新建Promise傳入的函數體
try { fn(resolve, reject); } catch (e){ reject(e) }
在執行過程當中可能會遇到錯誤,須要捕獲錯誤傳給reject
promise必須提供then方法來存取它當前或最終的值或者緣由。
promise的then方法接收兩個參數:
promise.then(onFulfilled, onRejected)
現根據這些要求咱們先實現個簡單的then函數:
MyPromise.prototype.then = function (onFulfilled, onRejected) { const that = this onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r } if (that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled) that.rejectedCallbacks.push(onRejected) } if (that.state === RESOLVED) { onFulfilled(that.value) } if (that.state === REJECTED) { onRejected(that.value) } }
如上咱們就完成了一個簡易版的promise,可是還不能徹底知足Promise/A+規範,接下來咱們繼續完善
— 2.2.6.1 若是/當 promise 完成執行(fulfilled),各個相應的onFulfilled回調 必須根據最原始的then 順序來調用
— 2.2.6.2 若是/當 promise 被拒絕(rejected),各個相應的onRejected回調 必須根據最原始的then 順序來調用
promise2 = promise1.then(onFulfilled, onRejected);
接下來根據規範需求繼續完善then函數裏的代碼:
if (that.status === 'PENDING') { promise2 = new Promise(function (resolve, reject) { that.resolvedCallbacks.push(function () { setTimeout(function () { try { let x = onFulfilled(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); that.rejectedCallbacks.push(function () { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); }) } if (that.status === 'RESOLVED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { //用setTimeOut實現異步 try { let x = onFulfilled(that.value); //x多是普通值 也多是一個promise, 還多是別人的promise resolutionProcedure(promise2, x, resolve, reject) //寫一個方法統一處理 } catch (e) { reject(e); } }) }) } if (that.status === 'REJECTED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }) }
2.3.3.3 若是then是一個方法,把x看成this來調用它, 第一個參數爲 resolvePromise,第二個參數爲rejectPromise,其中:
2.3.3.3.4 若是調用then拋出一個異常e,
若是一個promise被一個thenable resolve,而且這個thenable參與了循環的thenable環,
[[Resolve]](promise, thenable)的遞歸特性最終會引發[[Resolve]](promise, thenable)再次被調用。
遵循上述算法會致使無限遞歸,鼓勵(但不是必須)實現檢測這種遞歸併用包含信息的TypeError做爲reason拒絕(reject)
這部分規範主要描述了resolutionProcedure函數的規範,下面咱們來實現resolutionProcedure這個函數,我先我麼你關注2.3.4下面那段話,簡單的來講規定了x不能與promise2相等,這樣會發生循環引用的問題,以下栗子:
let p = new MyPromise((resolve, reject) => { resolve(1) }) let p1 = p.then(value => { return p1 })
因此咱們須要先進行檢測,代碼以下:
function resolutionProcedure(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Error')) } }
接下來咱們判斷x的類型
if (x instanceof MyPromise) { x.then(function (value) { resolutionProcedure(promise2, value, resolve, reject) }, reject) }
若是 x 爲 Promise 的話,須要判斷如下幾個狀況:
最後咱們來完成剩餘的代碼:
let called = false if (x !== null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then if (typeof then === 'function') { then.call( x, y => { if (called) return called = true resolutionProcedure(promise2, y, resolve, reject) }, e => { if (called) return called = true reject(e) } ) } else { resolve(x) } } catch (e) { if (called) return called = true reject(e) } } else { resolve(x) }
有專門的測試腳本能夠測試所編寫的代碼是否符合PromiseA+的規範
首先,在promise實現的代碼中,增長如下代碼:
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.js
那麼在對應的目錄執行如下命令:
promises-aplus-tests promise.js
共有872條測試用例,能夠完美經過
這樣咱們就完成了符合Promise/A+規範的源碼,下面是整個代碼:
const PENDING = 'pending'; const RESOLVED = 'resolve'; const REJECTED = 'rejected'; function Promise(fn) { let that = this; that.status = 'PENDING'; that.value = undefined; that.resolvedCallbacks = []; that.rejectedCallbacks = []; function resolve(value) { if (value instanceof Promise) { return value.then(resolve, reject) } if (that.status === 'PENDING') { that.status = 'RESOLVED'; that.value = value; that.resolvedCallbacks.map(cb => cb(that.value)); } } function reject(value) { if (that.status === 'PENDING') { that.status = 'REJECTED'; that.value = value; that.rejectedCallbacks.map(cb => cb(that.value)); } } try { fn(resolve, reject); } catch (e) { reject(e); } } function resolutionProcedure(promise2, x, resolve, reject) { //有可能這裏返回的x是別人的promise 要儘量容許其餘人亂寫 if (promise2 === x) {//這裏應該報一個循環引用的類型錯誤 return reject(new TypeError('循環引用')); } //看x是否是一個promise promise應該是一個對象 let called; //表示是否調用過成功或者失敗 if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //多是promise 看這個對象中是否有then 若是有 姑且做爲promise 用try catch防止報錯 try { let then = x.then; if (typeof then === 'function') { //成功 then.call(x, function (y) { if (called) return //避免別人寫的promise中既走resolve又走reject的狀況 called = true; resolutionProcedure(promise2, y, resolve, reject) }, function (err) { if (called) return called = true; reject(err); }) } else { resolve(x) //若是then不是函數 則把x做爲返回值. } } catch (e) { if (called) return called = true; reject(e) } } else { //普通值 return resolve(x) } } Promise.prototype.then = function (onFulfilled, onRejected) { //成功和失敗默認不傳給一個函數 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) { return value; } onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err; } let that = this; let promise2; //新增: 返回的promise if (that.status === 'PENDING') { promise2 = new Promise(function (resolve, reject) { that.resolvedCallbacks.push(function () { setTimeout(function () { try { let x = onFulfilled(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); that.rejectedCallbacks.push(function () { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); }) } if (that.status === 'RESOLVED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { //用setTimeOut實現異步 try { let x = onFulfilled(that.value); //x多是普通值 也多是一個promise, 還多是別人的promise resolutionProcedure(promise2, x, resolve, reject) //寫一個方法統一處理 } catch (e) { reject(e); } }) }) } if (that.status === 'REJECTED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }) } return promise2; } Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = Promise;
以上就是符合Promise/A+規範的源碼,ES6的Promise其實並非向咱們這樣經過js來實現,而是在底層實現,而且還擴展了不少新的方法:
這裏就不一一介紹啦,你們能夠參考阮一峯老師的文章 ES6入門-Promise對象
這篇文章給你們講解的Promise/A+規範的源碼,但願你們能多讀多寫,深入的體會一下源碼的思想,對之後的開發也頗有幫助。
感謝你們的閱讀,以爲還不錯,辛苦點一下關注,謝謝!