理解一個東西最好的辦法之一就是動手本身寫,So,他(Promise)來了。廢話很少說,來看如何實現。面試
「五嶽歸來不看山,黃山歸來不看嶽。」但願看完這篇,你就不用再去看其餘 Promise 的實現原理了。數組
先來看Promise
用法:promise
new Promise((resolve, reject) => { resolve('hello'); // or reject('hello') }) .then(res => {}) .catch(err => {}) -----------分割線 // 分解一下,也就是下面這樣 let executor = (resolve, reject) => { resolve('hello'); // or reject('hello') } new Promise(executor) .then(res => {}) .catch(err => {}) 複製代碼
咱們來分析一下他有哪些功能/特性:瀏覽器
接下來,咱們要一一撕下他的假裝,揭開他的真面目。bash
基於上面分析結果,咱們先來實現前面三個功能:markdown
class Promise { constructor(executor) { // 定義 resolve let resolve = res => {} // 定義 reject let reject = err => {} // 自動執行 executor(resolve, reject); } } // 測試一下: new Promise((resolve, reject) => { console.log('執行到啦~') }) 複製代碼
能夠將上面代碼複製到控制檯執行,查看效果:異步
Ok,fine,接下來,咱們來實現她的三種狀態。函數
promise 狀態有以下特色:
1.promise 對象初始化狀態爲 pendingoop
2.當調用resolve(成功),會由pending => fulfilled測試
3.當調用reject(失敗),會由pending => rejected
Promsie 狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變
class Promise { constructor(executor) { this.status = "pending"; // 默認狀態 this.value; // resolve 成功時的值 this.error; // reject 失敗時的值 let resolve = res => { if(this.status === "pending") { this.value = res; this.status = "resolved"; } } let reject = err => { if(this.status === "pending") { this.error = err; this.status = "rejected"; } } executor(resolve, reject); } } 複製代碼
測試一下,若是不去resolve,也不去reject
// 測試一下:
new Promise((resolve, reject) => {
})
複製代碼
那麼Promise應該是初始狀態。咱們將上面代碼執行測試一下,獲得結果以下:
此時狀態是:{status: "pending"}
。
當咱們執行 resolve
// 測試一下: new Promise((resolve, reject) => { resolve('成功啦~'); }) 複製代碼
將獲得結果以下:
當執行 reject
// 測試一下: new Promise((resolve, reject) => { resolve('失敗啦~') }) 複製代碼
Promise 這個對象有 then 方法,仍是先來分析,then 有什麼?
promise.then(onFulfilled, onRejected); // 這裏假設 promise 繼承於 Promise 類
複製代碼
咱們繼續在前面 Promise 類中書寫 then 方法:
class Promise { constructor(executor) { this.status = "pending"; // 默認promise狀態 this.value; // resolve成功時的值 this.error; // reject失敗時的值 let resolve = res => { if(this.status === "pending") { this.value = res; this.status = "resolved"; } } let reject = err => { if(this.status === "pending") { this.error = err; this.status = "rejected"; } } executor(resolve, reject) } // 聲明 then then(onFullfilled, onRejected) { if(this.status === "resolved") { onFullfilled(this.value) } if(this.status === "rejected") { onRejected(this.error) } } } 複製代碼
測試一下:
new Promise((resolve, reject) => { resolve("成功啦~"); // 或 reject("失敗啦~") }) .then(res => { console.log(res); }, err => { console.log(err); }) 複製代碼
獲得結果:
至此,基本實現簡單的同步代碼,可是當 resolve 在 setTimeout
內執行,then 時 state 仍是 pending 等待狀態。咱們就須要在 then 調用的時候,將成功和失敗存到各自的數組,一旦 reject 或者 resolve,就調用它們。
相似於分佈訂閱,先將 then 內的兩個函數存儲,因爲 promise 能夠有多個 then,因此存在同一個數組內。當成功或失敗的時候用 forEach 調用他們。
class Promise { constructor(executor) { this.status = "pending"; // 默認promise狀態 this.value; // resolve成功時的值 this.error; // reject失敗時的值 + this.resolveQueue = []; // 成功存放的數組 + this.rejectQueue = []; // 失敗存放法數組 let resolve = value => { if(this.status === "pending") { this.value = value; this.status = "resolved"; // 一旦resolve執行,調用成功數組的函數 + this.resolveQueue.forEach(fn => fn()); } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; } // 一旦reject執行,調用失敗數組的函數 + this.rejectQueue.forEach(fn => fn()); } executor(resolve, reject) } // 執行到then的時候 then(onFullfilled, onRejected) { if(this.status === "resolved") { this.resolveQueue.push(() => { onFullfilled(this.value); }) } if(this.status === "rejected") { this.rejectQueue.push(() => { onRejected(this.error); }) } // 當狀態state爲pending時 + if(this.status === "pending") { // onFulfilled傳入到成功數組 + this.resolveQueue.push(() => { + onFullfilled(this.value); + }) // onRejected傳入到失敗數組 + this.rejectQueue.push(() => { + onRejected(this.error); + }) + } } } 複製代碼
咱們經常用到new Promise().then().then()
這樣的寫法,這就是鏈式調用,原來是用於解決地獄回調的。那麼如何去實現呢? 爲了達到這個效果,咱們能夠再第一個 then 函數內再返回一個 Promise
,讓這個新的 Promise
返回的值傳遞到下一個 then 中。
一句話總結:
經過在 then 中 return 一個新的
Promise
,從而實現 then 的鏈式調用!
代碼以下:
class Promise { constructor(executor) { this.status = "pending"; // 默認promise狀態 this.value; // resolve 成功時的值 this.error; // reject 失敗時的值 this.resolveQueue = []; // 成功時回調隊列 this.rejectQueue = []; // 失敗時回調隊列 let resolve = value => { if(this.status === "pending") { this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { let promise2; if(this.status === "resolved") { promise2 = new Promise((resolve, reject) => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) } if(this.status === "rejected") { promise2 = new Promise((resolve, reject) => { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); }) } if(this.status === "pending") { promise2 = new Promise((resolve, reject) => { this.resolveQueue.push(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectQueue.push(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }) }) } return promise2; } } -------------------分割線 // 將上面代碼整理一下 class Promise { constructor(executor) { this.status = "pending"; // 默認promise狀態 this.value; // resolve成功時的值 this.error; // reject失敗時的值 this.resolveQueue = []; // 成功時回調隊列 this.rejectQueue = []; // 失敗時回調隊列 let resolve = value => { if(this.status === "pending") { this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { let promise2; promise2 = new Promise((resolve, reject) => { if(this.status === "resolved") { let x = onFullfilled(this.value); // resolvePromise函數,處理本身return的promise和默認的promise2的關係 resolvePromise(promise2, x, resolve, reject); } if(this.status === "rejected") { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); } if(this.status === "pending") { this.resolveQueue.push(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectQueue.push(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }) } }); // 返回 promise,達成鏈式效果 return promise2; } } 複製代碼
最後,咱們來完成上面的 resolvePromise 函數,咱們暫且將第一個 then 返回的值成爲 x,在這個函數中,咱們須要去判斷 x 是否是 promise(這裏是重點!):
resolvePromise 代碼以下:
/** * 處理promise遞歸的函數 * * promise2 {Promise} 默認返回的promise * x {*} 咱們本身 return 的對象 * resolve * reject */ function resolvePromise(promise2, x, resolve, reject){ // 循環引用報錯 if(x === promise2){ // reject 報錯拋出 return reject(new TypeError('Chaining cycle detected for promise')); } // 鎖,防止屢次調用 let called; // x 不是 null 且 x 是對象或者函數 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+ 規定,聲明then = x的then方法 let then = x.then; // 若是then是函數,就默認是promise了 if (typeof then === 'function') { // then 執行 第一個參數是 this 後面是成功的回調 和 失敗的回調 then.call(x, y => { // 成功和失敗只能調用一個 if (called) return; called = true; // 核心點2:resolve 的結果依舊是 promise 那就繼續遞歸執行 // 核心點2:resolve 的結果依舊是 promise 那就繼續遞歸執行 // 核心點2:resolve 的結果依舊是 promise 那就繼續遞歸執行 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失敗只能調用一個 if (called) return; called = true; reject(err);// 失敗了就失敗了 }) } else { resolve(x); // 直接成功便可 } } catch (e) { // 走到 catch 也屬於失敗 if (called) return; called = true; // 取then出錯了那就不要在繼續執行了 reject(e); } } else { resolve(x); } } 複製代碼
完整測試代碼以下,能夠複製進瀏覽器控制檯執行下:
function resolvePromise(promise2, x, resolve, reject){ // 循環引用報錯 if(x === promise2){ // reject 報錯拋出 return reject(new TypeError('Chaining cycle detected for promise')); } // 鎖,防止屢次調用 let called; // x不是null 且x是對象或者函數 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+ 規定,聲明then = x的then方法 let then = x.then; // 若是then是函數,就默認是promise了 if (typeof then === 'function') { // 就讓then執行 第一個參數是this 後面是成功的回調 和 失敗的回調 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) { // 也屬於失敗 if (called) return; called = true; // 取then出錯了那就不要在繼續執行了 reject(e); } } else { resolve(x); } } class Promise { constructor(executor) { this.status = "pending"; // 默認promise狀態 this.value; // resolve成功時的值 this.error; // reject失敗時的值 this.resolveQueue = []; // 成功時回調隊列 this.rejectQueue = []; // 失敗時回調隊列 let resolve = value => { if(this.status === "pending") { this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { let promise2; promise2 = new Promise((resolve, reject) => { if(this.status === "resolved") { let x = onFullfilled(this.value); // resolvePromise函數,處理本身return的promise和默認的promise2的關係 resolvePromise(promise2, x, resolve, reject); } if(this.status === "rejected") { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); } if(this.status === "pending") { this.resolveQueue.push(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.rejectQueue.push(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }) } }); // 返回 promise,達成鏈式效果 return promise2; } } // 測試如下代碼 new Promise((resolve, reject) => { resolve(); }).then((res)=>{ console.log('進入第一個then!') return new Promise((resolve,reject)=>{ resolve('hello world'); }) }).then((res)=>{ console.log('進入第二個then!', res); }) 複製代碼ok,咱們實現了 then 的鏈式調用,這也是實現 Promise 中的重難點!
核心思路:
用setTimeout解決異步問題
代碼以下:
class Promise { constructor(executor) { this.status = "pending"; // 默認promise狀態 this.value; // resolve成功時的值 this.error; // reject失敗時的值 this.resolveQueue = []; // 成功時回調隊列 this.rejectQueue = []; // 失敗時回調隊列 let resolve = value => { if(this.status === "pending") { this.value = value; this.status = "resolved"; this.resolveQueue.forEach(fn => fn()) } } let reject = value => { if(this.status === "pending") { this.error = value; this.status = "rejected"; this.rejectQueue.forEach(fn => fn()) } } executor(resolve, reject) } then(onFullfilled, onRejected) { let promise2; promise2 = new Promise((resolve, reject) => { if(this.status === "resolved") { // 異步 + setTimeout(() => { let x = onFullfilled(this.value); // resolvePromise函數,處理本身return的promise和默認的promise2的關係 resolvePromise(promise2, x, resolve, reject); + }, 0) } if(this.status === "rejected") { // 異步 + setTimeout(() => { let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); + }, 0) } if(this.status === "pending") { this.resolveQueue.push(() => { // 異步 + setTimeout(() => { let x = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); + }, 0) }) this.rejectQueue.push(() => { // 異步 + setTimeout(() => { let x = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); + }, 0) }) } }); // 返回 promise,達成鏈式效果 return promise2; } } 複製代碼
new Promise((resolve, reject)=>{ resolve('YoYo'); }).then().then().then().then().then().then().then((res)=>{ console.log(res); }) 複製代碼
當執行上面多個 then,咱們指望最後那個 then 打印出 'YoYo'。
實現很簡單:onFulfilled 若是不是函數,就忽略 onFulfilled,直接返回 value!
相應的,咱們也要處理下沒有 onRejected 的狀況:onRejected 若是不是函數,就忽略 onRejected,直接扔出錯誤!
代碼以下,在以前的 Promise 類的 then 加入:
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err;} <!--... 省略--> } 複製代碼
核心思路:
catch 是失敗的回調,至關於執行 this.then(null,fn)
class Promise { constructor(executor) { <!--... 省略--> } then(onFullfilled, onRejected) { <!--... 省略--> } + catch(onRejected) { + return this.then(null, onRejected) + } } 複製代碼
此外,咱們還須要對其餘幾個函數外使用 try/catch 去作異常捕獲,這裏不展開,理解便可(本文最後源碼中會展現)。
這是一道經典面試題!
Promise.all()
接收一個數組做爲參數,該方法返回一個 Promise
實例,此實例在 iterable 參數內全部的 promise 都「完成(resolved)」或參數中不包含 promise 時回調完成(resolve);若是參數中 promise 有一個失敗(rejected),此實例回調失敗(reject),失敗的緣由是第一個失敗 promise 的結果。
用法以下:
var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // [3, 1337, "foo"] }); 複製代碼
接下來看看如何實現: 下面手寫的,沒有測試,回頭補充!下班先~
Promise.all = function(promises) { let count = 0; let res = []; return new Promise((resolve, reject) => { for(let i = 0; i<promises.length; i++) { promises[i].then(res => { res.push(res); count++; if(count === promises.length) resolve(res); }) } }) .catch(err => { reject(err); }) } 複製代碼
Promise.race() 它一樣接收一個promise對象組成的數組做爲參數,並返回一個新的promise對象。一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。
Promise.race = function(promises) { return new Promise((resolve, reject) => { for(let i = 0; i<promises.length; i++) { promises[i].then(resolve, reject); } }) } 複製代碼
Promise.resolve = function(value) { return new Promise((resolve, reject) => { resolve(value); }) } 複製代碼
Promise.reject = function(value) { return new Promise((resolve, reject) => { reject(value); }) } 複製代碼
當課後做業吧,同窗們本身寫,能夠放回復中哈~
Reference: Promises/A+