~
Promise 篇
本文皆在實現Promise的所有方法,程式碼均測試可執行,編寫於2019年11月17日
GitHub
倉庫更有自己實現的webpack、mini-react、redux、react-redux、websocket , Electron
跨平臺桌面端、React-native
移動端開源專案等
倉庫地址:
https://github.com/JinJieTan
回顧Promise
:
new Promise
中的構造器函式同步執行
then
的回撥函式以微任務的形式執行
呼叫resolve,reject
後,狀態不能再被改變,傳遞的值也是
每次.then
後返回的還是一個promise
promise
可以巢狀
其餘後面會談到
正式開始:
乞丐版:
編寫邏輯:
1.Promise
被 new
呼叫
2.每次失敗或者成功需要指定回撥函式,並且可以傳遞值
3.Promise
擁有.then
方法
上面程式碼有個問題,狀態改變應該是非同步的,.then應該是微任務形式執行
非同步改變狀態並且支援三種狀態版本:
編寫思路
狀態只能由Pending
改變,而且只能改變一次
非同步改變狀態,非同步的執行.then
支援鏈式呼叫
寫到這裡,需要暫停,捋一捋思路。
萬事開頭難,其實編寫一個Promise
是非常簡單的事情,看懂上面這兩段程式碼,然後徹底搞清楚這三點,再往下看。
Promise
的構造器函式是同步執行
resolve、reject
的呼叫是同步呼叫,非同步執行.例如resolve()
是同步呼叫了resolve
這個函式,但是resolve
函式內部的程式碼是非同步的。---即非同步改變狀態,非同步執行.then
new Promise((resolve,reject)=>{
console.log('這裡是同步執行')
resolve(‘resolve函式內部是非同步執行’)
}).then(()=>{
console.log('這裡等resolve函式內部非同步執行,狀態改變以後再執行')
})
徹底搞懂上面的這個例子和兩句話,然後你就可以往下看了,其實下面也都是一些重複或者細節處理的工作
支援.then
的鏈式呼叫
想支援鏈式操作,其實很簡單,首先儲存回撥時要改為使用陣列
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = []
當然執行回撥時,也要改成遍歷回撥陣列執行回撥函式
最後,then
方法也要改一下,只需要在最後一行加一個return this
即可,這其實和jQuery
鏈式操作的原理一致,每次呼叫完方法都返回自身例項,後面的方法也是例項的方法,所以可以繼續執行。
編寫思路
1.將所有的成功與失敗回撥函式存在陣列中,遍歷執行
2.為了支援鏈式呼叫,返回this
例項物件即可
3.每次改變狀態,清空所有的佇列回撥
4.目前這種方式只支援同步的回撥,下面會加入支援非同步
非同步鏈式呼叫,好像是這裡最難的點,但是在應用層裡的難點,加一箇中間層就能解決,實在不行加兩個 ---來自國內不知名碼農
看下面這段Node.js
程式碼:
上面場景,我們讀取完1.txt
後並列印1.txt
內容,再去讀取2.txt
並列印2.txt
內容,再去讀取3.txt
並列印3.txt
內容,而讀取檔案都是非同步操作,所以都是返回一個promise
。
我們上一節實現的promise
可以實現執行完非同步操作後執行後續回撥,但是本節的回撥讀取檔案內容操作並不是同步的,而是非同步的,所以當讀取完1.txt
後,執行它回撥onFulfilledCallbacks
裡面的f1,f2,f3
時,非同步操作還沒有完成,所以我們本想得到這樣的輸出:
this is 1.txt
this is 2.txt
this is 3.txt
但是實際上卻會輸出
this is 1.txt
this is 1.txt
this is 1.txt
上面遇到的問題,有點像一個面試題,考閉包的一個迴圈。看列印輸出幾,大家應該有印象。
支援非同步鏈式呼叫
每次.then
返回一個新的promise
,這個新的promise
擁有它獨自對應的成功和失敗回撥(相當於中間層)
同樣每次狀態改變就清空當前promise
對應的回撥佇列
修改.then
方法
.then
在鏈式呼叫中會被執行多次,這裡是本文的重點
思路解析:
首先判斷當前Promise
的狀態,如果狀態沒有改變,那就全部新增到佇列中
呼叫resolve
函式,同樣清空佇列中所有的任務,不同點在於bridgePromise
這個用來橋接的Promise
(看成中間層),下面給出例子詳解
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
let bridgePromise;
//防止使用者不傳成功或失敗回撥函式,所以成功失敗回撥都給了預設回撥函式
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
if (self.status === FULFILLED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
if (self.status === REJECTED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status === PENDING) {
return bridgePromise = new MyPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}
這個例子,裡面有非同步的程式碼,但是Promise
可以做到有序執行
我們看bridgePromise
的原始碼:
程式碼很簡單 就是遞迴呼叫,直到返回的不是一個Promise
,那麼呼叫resolve
清空佇列,並且把返回的值儲存在self
屬性上,提供給下一個任務使用。
下面就是流程圖:
這裡一定要看清楚,本文的重點基本都在這裡。
符合Promise A+
規範,修改resolvePromise
函式即可
function resolvePromise(bridgepromise, x, resolve, reject) {
//2.3.1規範,避免迴圈引用
if (bridgepromise === x) {
return reject(new TypeError('Circular reference'));
}
let called = false;
//這個判斷分支其實已經可以刪除,用下面那個分支代替,因為promise也是一個thenable物件
if (x instanceof MyPromise) {
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
reject(error);
});
} else {
x.then(resolve, reject);
}
// 2.3.3規範,如果 x 為物件或者函式
} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
// 是否是thenable物件(具有then方法的物件/函式)
//2.3.3.1 將 then 賦為 x.then
let then = x.then;
if (typeof then === 'function') {
//2.3.3.3 如果 then 是一個函式,以x為this呼叫then函式,且第一個引數是resolvePromise,第二個引數是rejectPromise
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
if (called) return;
called = true;
reject(error);
})
} else {
//2.3.3.4 如果 then不是一個函式,則 以x為值fulfill promise。
resolve(x);
}
} catch (e) {
//2.3.3.2 如果在取x.then值時丟擲了異常,則以這個異常做為原因將promise拒絕。
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
實現es6 promise的all,race,resolve,reject
方法
all
方法:
race
方法
resolve,reject
方法~ 快速定義一個成功或者失敗狀態的Promise
實現promisify
方法~
實現catch
方法~其實就是不傳第一個引數.then
方法的語法糖
到這裡,一個Promise
並且符合A+規範的所有方法就實現了,網上的實現方式有很多都不一樣,本文以一個比較簡單明瞭的方式去實現了它。希望能幫助到大家更清楚的瞭解Promise
後面會針對下面的內容出專題系列文章~ 歡迎關注本公眾號:前端巔峰
1.從零手寫一個React
2.從零編寫一個webpack
3.從零實現一個websocket
4.從零實現一個vue
5.優雅的讓react和vue一起開發
本公眾號注重關注技術方向
即時通訊
Electron跨平臺桌面端、React-native移動端、Taro小程式
Node.js全棧工程師方向、分散式微服務
原生JavaScript