首先先看一下 promise 的調用方式:
// 實例化 Promise: new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) //這里相當於給value賦值 }, 0) }).then(value => { console.log(value) })
實現原理如下:
const PENDING = 'pending' //首先我們創建了三個常量用於表示狀態,對於經常使用的一些值都應該通過常量來管理,便於開發及后期維護 const RESOLVED = 'resolved' const REJECTED = 'rejected' function MyPromise(fn) { const that = this //在函數體內部首先創建了常量 `that`,因為代碼可能會異步執行,用於獲取正確的 `this` 對象 that.state = PENDING //一開始 `Promise` 的狀態應該是 `pending` that.value = null //`value` 變量用於保存 `resolve` 或者 `reject` 中傳入的值 that.resolvedCallbacks = [] that.rejectedCallbacks = [] /* `resolvedCallbacks` 和 `rejectedCallbacks` 用於保存 `then` 中的回調, 因為當執行完 `Promise` 時狀態可能還是等待中,這時候應該把 `then` 中的回調保存起來用於狀態改變時使用 */ function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value))//map這里是執行回調函數 } } function reject(value) { if (that.state === PENDING) { that.state = REJECTED that.value = value that.rejectedCallbacks.map(cb => cb(that.value))//map這里是執行回調函數 } } /* * 首先兩個函數都得判斷當前狀態是否為等待中,因為規范規定只有等待態才可以改變狀態 * 將當前狀態更改為對應狀態,並且將傳入的值賦值給 `value` * 遍歷回調數組並執行 */ try { fn(resolve, reject) } catch (e) { reject(e) } } MyPromise.prototype.then = function(onFulfilled, onRejected) { const that = this //首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數 //當參數不是函數類型時,需要創建一個函數賦值給對應的參數,同時也實現了透傳 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => { return v } onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r } //接下來就是一系列判斷狀態的邏輯,當狀態不是等待態時,就去執行相對應的函數。 //如果狀態是等待態的話,就往回調函數中 `push` 函數,比如如下代碼就會進入等待態的邏輯 if (that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled)//push這里是保存回調函數 that.rejectedCallbacks.push(onRejected) } if (that.state === RESOLVED) { onFulfilled(that.value) } if (that.state === REJECTED) { onRejected(that.value) } }
詳細解釋如下:
定義異步函數 MyPromise,所以執行的函數也是 MyPromise:
首先看 函數執行的方法:
new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 0) })
函數的參數是:
(resolve, reject) => { setTimeout(() => { resolve(1) }, 0) }
對應着
function MyPromise(fn){ try { fn(resolve, reject) // 在這里執行了傳入的參數fn(),並且把規定的 resolve, reject 兩個參數傳遞到 fn 中。 } catch (e) { reject(e) } }
中的 fn,所以會執行這個傳入的函數 fn(resolve, reject);
傳入的參數是異步的,會在同步代碼結束后再去執行對應的 resolve(1)這個函數,
而這個函數已經在 MyPromise 中進行了定義:
function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value))//map這里是執行回調函數 } }
// 也就是會在同步代碼之后再執行上面的函數,所以我們繼續看 MyPromise 的調用
new MyPromise((resolve, reject) => { //異步代碼 }).then(value => { console.log(value) })
接着執行方法: then()。而then 掛在了原型鏈上:
MyPromise.prototype.then = function(onFulfilled, onRejected) { }
所以then包含兩個參數,分別對對應 onFulfilled, onRejected。 如果沒有定義需要做容錯處理,
也就是給默認的函數參數:
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => { return v }
由於執行同步操作,此時state 還等於PENDING,所以執行:
if (that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled)//push這里是保存回調函數 that.rejectedCallbacks.push(onRejected) }
也就是把 成功后操作函數和失敗函數分別保存到對應的數組中。
setTimeout(() => { resolve(1) }, 0)
好了,接下來同步執行結束,然后開始執行異步操作:
function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value))//map這里是執行回調函數 } }
可以看到,先改變狀態,再從保存數組中,獲取到回調函數,再執行!
至此,在promise中 resolve(1) 告訴了執行回調的時機和參數。
而then規定的是異步之后的回調函數。
然后我們看到還在then函數中規定了其他的狀態,解釋一下:
如果執行函數中,沒有異步處理:
new MyPromise((resolve, reject) => { resolve(1) }).then(value => { console.log(value) })
也就是在定義中:
try { fn(resolve, reject) } catch (e) { reject(e) }
直接執行了 fn(),根據傳入的 resolve,reject。這里直接執行了 resolve:
function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value))//map這里是執行回調函數 } }
更改了狀態,由於還沒有執行then函數,保存函數沒有數據,所以沒有可以執行的回調函數。
接下來程序走到了then
new MyPromise((resolve, reject) => { //同步代碼 }).then(value => { console.log(value) })
由於狀態已經改變,所以執行:
MyPromise.prototype.then = function(onFulfilled, onRejected) { if (that.state === RESOLVED) { onFulfilled(that.value) } }
同樣也做到了執行then傳入的函數。
總結一下,如果 resolve 被放在了異步函數中,then傳入的回調函數會先被保存下來,待異步函數執行完畢之后,
在次執行回調函數;
而如若 resolve 被放在了同步函數中,則回調函數數組為空,順序執行到 then 函數,則會執行該回調函數。