Promise 對象用於表示一個異步操作的最終完成 (或失敗), 及其結果值.它是用於解決回調地獄的尷尬與醜陋的神器。
這裏附上MDN地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise。
可以看下具體的用法。但是今天我們試着手寫一個簡單(簡陋)的promise,來進一步認識她是如何處理異步操作的。
就像汽車行業的逆向研發一樣,我們也是根據 promise使用的API,來倒推出promise源碼,這樣會比較好理解。
1、promise的用法
想要寫源碼,我們其實根據其API倒退我們準備寫的源碼。畢竟我們現在是在逆向開發。所以看看我們用常用的promise用法。先看一下最常用的以下兩種場景:
//場景一:直接調用 -> 這就意味着在寫源碼的時候 Promise方法上本身綁定的就有 resolve 或者 reject的屬性
Promise.resolve(1000); // -> 成功的值
Promise.reject('some error'); // -> 失敗的值
//場景二:實例化 -> 這就意味着原型方法上要有個 then的方法
new Promise((resolve, reject) => {
// 這裏來個異步的代碼
settimeout(() => {
resolve(1000);
},1000)
}).then(res => {
console.log('接受的結果', res)
})
2、Promise 構造函數的‘逆向研發’
我們先來打一個基本的架子 - 構造函數。
// 毫無疑問, Promise是一個構造函數。
// 直接上構造函數的基本配置:大寫函數,原型方法
function Promise () {
}
// 上邊我們說了,實例方法 有個 then 的方法用來接收結果的,那麼 這個方法定義在 prototype上更合適
Promise.prototype.then = function () {
// 這裏接收結果
}
架子好了之後,我們要考慮這構造函數中都有些設麼東西。
A、狀態 status
我們知道promise內部的三大狀態: pending(等待),fulfilled(成功), rejected(失敗)。promise對象的狀態,從_Pending_轉換爲_Fulfilled_或_Rejected_之後, 這個promise對象的狀態就不會再發生任何變化。如下圖:
那麼構造函數理應有個 狀態(status)的屬性;
function Promise () {
// 默認的初始狀態
this.status = 'pending';
}
B、值(錯誤) value或error
有了狀態 之後,要有對應的值。
function Promise () {
// 狀態
this.status = 'pending';
// 成功的值 對應的是resolve ,默認爲 null
this.value = null;
// 失敗的原因 對應的是reject,默認爲null
this.reason = null;
}
C、構造函數傳參 executor
executor是帶有 resolve
和 reject
兩個參數的函數 。
說完了上邊的值和錯誤,肯定是有兩個函數分別用來處理對應的值: resolve(處理成功的值 - value),reject(處理錯誤的信息 - error)。注意,這兩個參數都是函數。
// executor -> 是一個函數 函數包含了resolve, reject兩個函數;
// 我們用promise的時候也是這樣的 new Promise((resolve, reject) => {resolve(1000)})
function Promise (executor ) {
if (typeof executor != 'function') {
throw new Error(`${executor} is not a function`)
}
this.status = 'PENDING';// 狀態
this.value = null; // 成功 resolve 的值
this.reason = null;// 失敗 reject 的值
this.resolve = (value) => {
// 確保狀態是 pending才能 更新狀態
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
}
}
this.reject = (reason) => {
// 確保狀態是 pending才能 更新狀態
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
}
}
// 執行這個傳遞進來的函數
executor(this.resolve, this.reject);
}
D、then 方法
不論是成功或是失敗都會調用then方法,用來接收傳遞過來的值。
Promise.prototype.then = function (onFulFilled, onRejected) {
// -> 成功時傳值的處理
if (this.status == 'fulfilled') {
if (typeof onFulFilled == 'function') {
onFulFilled(this.value)
}
}
// -> 失敗時傳值的處理
if (this.status == 'rejected') {
if (typeof onRejected == 'function') {
onRejected(this.reason)
}
}
}
好了,現在主體的方法基本處理完了。現在我們來試驗一下,我們寫的這個promise
// 場景一: -> 用來處理 同步的代碼
new Promise((resolve, reject) => {
console.log('promise 執行了') // -> 輸出成功,同步代碼通過了
})
// 但是我們一開始就說,promise的用處在於處理異步操作。
// 場景二: -> 用來處理 異步代碼
new Promise((resolve, reject) => {
settimeout(() => {
// 異步代碼操作 以成功傳值爲例
resolve(1000)
},1000)
}).then(res => console.log(res))
// -> 結果是 沒有任何輸出結果。
處理異步的時候,我們目前的代碼並不能執行。爲什麼呢?我們要注意,異步代碼並不會立即執行。而是在主體代碼執行之後,纔會來處理異步代碼。所以,在一開始執行then方法的時候,promise的狀態保持在 pending。那麼既然pending,因此then方法即沒有調用onFulfilled也沒有調用onRejected。所以這個值根本就不會被捕獲到。
那麼如何處理異步代碼?
F、處理異步代碼
上邊說到,then執行的時候 promise的狀態始終在 pending. 所以,並不能處理回調。那麼我們在他 pending的時候用一個數組,把它存起來,在 resolve 的時候執行就可以了。
function promise (executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
// 在這裏加上兩個暫存數組 分別存儲 resolve的異步函數和reject的異步函數
this.onFulFilledAry = [];
this.onRejectedAry = [];
this.resolve = value = > {
if (this.status == 'pending') {
this.value = value;
this.status = 'fulfilled'
// 在這裏執行所有resolve暫存的回調
this.onFulFilledAry.forEach(fn => fn(value))
}
}
this.reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
// 在這裏執行所有的reject的暫存回調
this.onRejectedAry.forEach(fn => fn(reason))
this.reason = reason;
}
}
executor(this.resolve, this.reject);
}
Promise.prototype.then = function (onFulFilled, onRejected) {
// 這裏主要處理異步代碼的執行
if (this.status == 'pending') {
if (typeof onFulFilled == 'function) {
// 成功的回調函數存進暫存的數組中
this.onFulFilledAry.push(onFulFilled )
}
if (typeof onRejected== 'function) {
// 失敗的回調函數存進暫存的數組中
this.onRejectedAry.push(onRejected)
}
}
// 下邊的resolve和reject的狀態的代碼不寫了
......
}
我們再次測試一下,異步的代碼。
let p = new Promise((resolve, reject) => {
settimeout(() => {
resolve(1000)
}, 1000)
})
p.then(res => console.log(res)) // -> 成功輸出 1000
至此,Promise已經支持了異步操作,promise最基本的功能已經基本實現了。
至於還有其他的API,比如 promise.all promise.race等等。後續有時間繼續探究分享。