前端面試的時候,常常能看到這樣一道題,實現一個Promise
。前端
這篇文章將一步步實現 Promise,完全弄懂 Promise。面試
平時使用 Promise 咱們能夠知道 Promise 存在三種狀態 Pending、Resolve、Reject,在 new Promise
時須要傳入一個函數, 參數爲 resolve
和 reject
的函數,這兩個函數用來改變 Promise 的狀態。數組
最重要的還有個 then
的方法,then
函數能夠傳入兩個函數做爲參數,第一個函數用來獲取異步操做的結果,第二個函數用來獲取錯誤的緣由。promise
除此以外還須要 value
和 reason
存放 Promise 的結果或錯誤緣由。markdown
從上面這些信息能夠轉化爲下面的代碼:異步
const PENDING = 'pending'; const RESOLVED = 'resolved'; const REJECTED = 'rejected'; class Promise { constructor(executor) { this.status = PENDING; this.value = null; this.reason = null; function resolve (value) { this.status = RESOLVED; this.value = value; }; function reject (reason) { this.status = REJECTED; this.reason = reason; }; executor(resolve.bind(this), reject.bind(this)); } then(onFulfilled, onRejected) { if (this.status === RESOLVED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } } } 複製代碼
Promise 的狀態只容許修改一次,那麼 resolve
和 reject
須要加上狀態判斷。函數
function resolve (value) { if (this.status !== PENDING) return; this.status = RESOLVED; this.value = value; }; function reject (reason) { if (this.status !== PENDING) return; this.status = REJECTED; this.reason = reason; }; 複製代碼
在調用 then
函數時,Promise 的狀態有可能仍是 Pending 的狀態,這時須要將 then
函數的兩個參數進行保存,狀態改變時在進行調用。then
函數有可能會調用屢次,那麼能夠用數組保存參數。oop
class Promise { constructor(executor) { // ... this.resolveCbs = []; this.rejectCbs = []; function resolve (value) { // ... this.resolveCbs.map(fn => fn(this.value)); }; function reject (reason) { // ... this.rejectCbs.map(fn => fn(this.reason)); }; } then(onFulfilled, onRejected) { // ... if (this.status === PENDING) { this.resolveCbs.push(onFulfilled); this.rejectCbs.push(onRejected); } } } 複製代碼
寫到這裏,一個最基本的 Promise 就可使用了。優化
new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 500); }).then(res => { console.log(res); }); 複製代碼
上面的代碼雖然完成了最基本的 Promise,可是還未實現 then
函數的鏈式調用。this
new Promise((resolve, reject) => { // ... }).then(res => { // ... }).then(res => { // ... }) 複製代碼
鏈式調用也是 Promise 的重點所在,由於有了鏈式調用,才能避免回調地獄的問題。接下來就來一步步實現。
then
是 Promise 的方法,爲了可以繼續調用 then
函數,須要 then
函數返回一個新的 Promise。
onFulfilled
或 onRejected
的返回值有可能也是一個 Promise,那麼須要等待 Promise 執行完的結果傳遞給下一個 then
函數。若是返回的不是 Promise,就能夠將結果傳遞給下一個 then
函數。
將 then
函數進行以下修改,resolvePromise 另外實現。
class Promise { // ... then(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === RESOLVED) { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } if (this.status === REJECTED) { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } if (this.status === PENDING) { this.resolveCbs.push(() => { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }); this.rejectCbs.push(() => { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }); } }); return promise2; } } 複製代碼
then(onFulfilled, onRejected) { function resolvePromise (promise2, x, resolve, reject) { if (promise2 === x) { // 不容許 promise2 === x; 避免本身等待本身 return reject(new TypeError('Chaining cycle detected for promise')); } // 防止重複調用 let called = false; try { if (x instanceof Promise) { let then = x.then; // 第一個參數指定調用對象 // 第二個參數爲成功的回調,將結果做爲 resolvePromise 的參數進行遞歸 // 第三個參數爲失敗的回調 then.call(x, y => { if (called) return; called = true; // resolve 的結果依舊是 Promise 那就繼續解析 resolvePromise(promise2, y, resolve, reject); }, err => { if (called) return; called = true; reject(err); }); } else { resolve(x); } } catch (e) { reject(e); } } // ... } 複製代碼
then
函數then
函數的 onFulfilled
和 onRejected
參數容許不傳.
Promise/A+ 規範要求 onFulfilled
和 onRejected
不能被同步調用,可使用 setTimeout
改成異步調用。
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => { return v }; onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; }; function resolvePromise (promise2, x, resolve, reject) {...} let promise2 = new Promise((resolve, reject) => { function fulfilled () { setTimeout(() => { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }, 0); }; function rejected () { setTimeout(() => { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }, 0); } if (this.status === RESOLVED) { fulfilled.call(this); } if (this.status === REJECTED) { rejected.call(this); } if (this.status === PENDING) { this.resolveCbs.push(fulfilled.bind(this)); this.rejectCbs.push(rejected.bind(this)); } }); return promise2; } 複製代碼
class Promise { // ... catch(fn) { this.then(null, fn); } static resolve (val) { return new Promise((resolve) => { resolve(val); }); } static reject (val) { return new Promise((resolve, reject) => { reject(val); }); } static race(promises) { return new Promise((resolve, reject) => { promises.map(promise => { promise.then(resolve, reject); }); }); } static all(promises) { let arr = []; let i = 0; return new Promise((resolve, reject) => { promises.map((promise, index) => { promise.then(data => { arr[index] = data; if (++i === promises.length) { resolve(arr); } }, reject); }) }) } } 複製代碼
BAT前端經典面試問題:史上最最最詳細的手寫Promise教程
若是你喜歡個人文章,但願能夠關注一下個人公衆號【前端develop】