本文是一起學習造輪子系列的第一篇,本篇我們將從零開始寫一個符合Promises/A+規範的promise,本系列文章將會選取一些前端比較經典的輪子進行原始碼分析,並且從零開始逐步實現,本系列將會學習Promises/A+,Redux,react-redux,vue,dom-diff,webpack,babel,kao,express,async/await,jquery,Lodash,requirejs,lib-flexible等前端經典輪子的實現方式,每一章原始碼都託管在github上,歡迎關注~
相關係列文章:
一起學習造輪子(一):從零開始寫一個符合Promises/A+規範的promise
一起學習造輪子(二):從零開始寫一個Redux
一起學習造輪子(三):從零開始寫一個React-Redux
本系列github倉庫:
一起學習造輪子系列github(歡迎star~)
前言
Promise 是非同步程式設計的一種解決方案,比傳統的解決方案回撥函式和事件更合理更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。本篇不注重講解promise的用法,關於用法,可以看阮一峰老師的ECMAScript 6系列裡面的Promise部分:
本篇主要講解如何從零開始一步步的實現promise各項特性及功能,最終使其符合Promises/A+規範,因為講解較細,所以文章略長。
另外,每一步的專案原始碼都在github上,可以對照參考,每一步都有對應的專案程式碼及測試程式碼,喜歡的話,歡迎給個star~
專案地址:本文程式碼的github倉庫
開始
本文promise裡用到的非同步操作的示例都是使用的node裡面的fs.readFile方法,在瀏覽器端可以使用setTimeout方法進行模擬非同步操作。
一. 基礎版本
目標
- 可以建立promise物件例項。
- promise例項傳入的非同步方法執行成功就執行註冊的成功回撥函式,失敗就執行註冊的失敗回撥函式。
實現
function MyPromise(fn) {
let self = this; // 快取當前promise例項
self.value = null; //成功時的值
self.error = null; //失敗時的原因
self.onFulfilled = null; //成功的回撥函式
self.onRejected = null; //失敗的回撥函式
function resolve(value) {
self.value = value;
self.onFulfilled(self.value);//resolve時執行成功回撥
}
function reject(error) {
self.error = error;
self.onRejected(self.error)//reject時執行失敗回撥
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
//在這裡給promise例項註冊成功和失敗回撥
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
module.exports = MyPromise
複製程式碼
程式碼很短,邏輯也非常清晰,在then中註冊了這個promise例項的成功回撥和失敗回撥,當promise reslove時,就把非同步執行結果賦值給promise例項的value,並把這個值傳入成功回撥中執行,失敗就把非同步執行失敗原因賦值給promise例項的error,並把這個值傳入失敗回撥並執行。
本節程式碼
二. 支援同步任務
我們知道,我們在使用es6 的promise時,可以傳入一個非同步任務,也可以傳入一個同步任務,但是我們的上面基礎版程式碼並不支援同步任務,如果我們這樣寫就會報錯:
let promise = new Promise((resolve, reject) => {
resolve("同步任務執行")
});
複製程式碼
為什麼呢?因為是同步任務,所以當我們的promise例項reslove時,它的then方法還沒執行到,所以回撥函式還沒註冊上,這時reslove中呼叫成功回撥肯定會報錯的。
目標
使promise支援同步方法
實現
function resolve(value) {
//利用setTimeout特性將具體執行放到then之後
setTimeout(() => {
self.value = value;
self.onFulfilled(self.value)
})
}
function reject(error) {
setTimeout(() => {
self.error = error;
self.onRejected(self.error)
})
}
複製程式碼
實現很簡單,就是在reslove和reject裡面用setTimeout進行包裹,使其到then方法執行之後再去執行,這樣我們就讓promise支援傳入同步方法,另外,關於這一點,Promise/A+規範裡也明確要求了這一點。
2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
本節程式碼
三. 支援三種狀態
我們知道在使用promise時,promise有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。另外,promise一旦狀態改變,就不會再變,任何時候都可以得到這個結果promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,如果改變已經發生了,你再對promise物件新增回撥函式,也會立即得到這個結果。
目標
- 實現promise的三種狀態。
- 實現promise物件的狀態改變,改變只有兩種可能:從pending變為fulfilled和從pending變為rejected。
- 實現一旦promise狀態改變,再對promise物件新增回撥函式,也會立即得到這個結果。
實現
//定義三種狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function MyPromise(fn) {
let self = this;
self.value = null;
self.error = null;
self.status = PENDING;
self.onFulfilled = null;
self.onRejected = null;
function resolve(value) {
//如果狀態是pending才去修改狀態為fulfilled並執行成功邏輯
if (self.status === PENDING) {
setTimeout(function() {
self.status = FULFILLED;
self.value = value;
self.onFulfilled(self.value);
})
}
}
function reject(error) {
//如果狀態是pending才去修改狀態為rejected並執行失敗邏輯
if (self.status === PENDING) {
setTimeout(function() {
self.status = REJECTED;
self.error = error;
self.onRejected(self.error);
})
}
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
} else if (this.status === FULFILLED) {
//如果狀態是fulfilled,直接執行成功回撥,並將成功值傳入
onFulfilled(this.value)
} else {
//如果狀態是rejected,直接執行失敗回撥,並將失敗原因傳入
onRejected(this.error)
}
return this;
}
module.exports = MyPromise
複製程式碼
首先,我們建立了三種狀態"pending","fulfilled","rejected",然後我們在reslove和reject中做判斷,只有狀態是pending時,才去改變promise的狀態,並執行相應操作,另外,我們在then中判斷,如果這個promise已經變為"fulfilled"或"rejected"就立刻執行它的回撥,並把結果傳入。
本節程式碼
四. 支援鏈式操作
我們平時寫promise一般都是對應的一組流程化的操作,如這樣:
promise.then(f1).then(f2).then(f3)
但是我們之前的版本最多隻能註冊一個回撥,這一節我們就來實現鏈式操作。
目標
使promise支援鏈式操作
實現
想支援鏈式操作,其實很簡單,首先儲存回撥時要改為使用陣列
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
複製程式碼
當然執行回撥時,也要改成遍歷回撥陣列執行回撥函式
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
複製程式碼
最後,then方法也要改一下,只需要在最後一行加一個return this即可,這其實和jQuery鏈式操作的原理一致,每次呼叫完方法都返回自身例項,後面的方法也是例項的方法,所以可以繼續執行。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
} else if (this.status === FULFILLED) {
onFulfilled(this.value)
} else {
onRejected(this.error)
}
return this;
}
複製程式碼
本節程式碼
五. 支援序列非同步任務
我們上一節實現了鏈式呼叫,但是目前then方法裡只能傳入同步任務,但是我們平常用promise,then方法裡一般是非同步任務,因為我們用promise主要用來解決一組流程化的非同步操作,如下面這樣的調取介面獲取使用者id後,再根據使用者id調取介面獲取使用者餘額,獲取使用者id和獲取使用者餘額都需要呼叫介面,所以都是非同步任務,如何使promise支援序列非同步操作呢?
getUserId()
.then(getUserBalanceById)
.then(function (balance) {
// do sth
}, function (error) {
console.log(error);
});
複製程式碼
目標
使promise支援序列非同步操作
實現
這裡為方便講解我們引入一個常見場景:用promise順序讀取檔案內容,場景程式碼如下:
let p = new Promise((resolve, reject) => {
fs.readFile('../file/1.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
let f1 = function(data) {
console.log(data)
return new Promise((resolve, reject) => {
fs.readFile('../file/2.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
}
let f2 = function(data) {
console.log(data)
return new Promise((resolve, reject) => {
fs.readFile('../file/3.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
}
let f3 = function(data) {
console.log(data);
}
let errorLog = function(error) {
console.log(error)
}
p.then(f1).then(f2).then(f3).catch(errorLog)
//會依次輸出
//this is 1.txt
//this is 2.txt
//this is 3.txt
複製程式碼
上面場景,我們讀取完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
複製程式碼
所以要想實現非同步操作序列,我們不能將回撥函式都註冊在初始promise的onFulfilledCallbacks裡面,而要將每個回撥函式註冊在對應的非同步操作promise的onFulfilledCallbacks裡面,用讀取檔案的場景來舉例,f1要在p的onFulfilledCallbacks裡面,而f2應該在f1裡面return的那個Promise的onFulfilledCallbacks裡面,因為只有這樣才能實現讀取完2.txt後才去列印2.txt的結果。
但是,我們平常寫promise一般都是這樣寫的: promise.then(f1).then(f2).then(f3)
,一開始所有流程我們就指定好了,而不是在f1裡面才去註冊f1的回撥,f2裡面才去註冊f2的回撥。
如何既能保持這種鏈式寫法的同時又能使非同步操作銜接執行呢?我們其實讓then方法最後不再返回自身例項,而是返回一個新的promise即可,我們可以叫它bridgePromise,它最大的作用就是銜接後續操作,我們看下具體實現程式碼:
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);
}
});
});
}
}
//catch方法其實是個語法糖,就是隻傳onRejected不傳onFulfilled的then方法
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
//用來解析回撥函式的返回值x,x可能是普通值也可能是個promise物件
function resolvePromise(bridgePromise, x, resolve, reject) {
//如果x是一個promise
if (x instanceof MyPromise) {
//如果這個promise是pending狀態,就在它的then方法裡繼續執行resolvePromise解析它的結果,直到返回值不是一個pending狀態的promise為止
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgePromise, y, resolve, reject);
}, error => {
reject(error);
});
} else {
x.then(resolve, reject);
}
//如果x是一個普通值,就讓bridgePromise的狀態fulfilled,並把這個值傳遞下去
} else {
resolve(x);
}
}
複製程式碼
首先,為防止使用者不傳成功回撥函式或不失敗回撥函式,我們給了預設回撥函式,然後無論當前promise是什麼狀態,我們都返回一個bridgePromise用來銜接後續操作。
另外執行回撥函式時,因為回撥函式既可能會返回一個非同步的promise也可能會返回一個同步結果,所以我們把直接把回撥函式的結果託管給bridgePromise,使用resolvePromise方法來解析回撥函式的結果,如果回撥函式返回一個promise並且狀態還是pending,就在這個promise的then方法中繼續解析這個promise reslove傳過來的值,如果值還是pending狀態的promise就繼續解析,直到不是一個非同步promise,而是一個正常值就使用bridgePromise的reslove方法將bridgePromise的狀態改為fulfilled,並呼叫onFulfilledCallbacks回撥陣列中的方法,將該值傳入,到此非同步操作就銜接上了。
這裡很抽象,我們還是以檔案順序讀取的場景畫一張圖解釋一下流程:
當執行p.then(f1).then(f2).then(f3)
時:
- 先執行p.then(f1)返回了一個bridgePromise(p2),並在p的onFulfilledCallbacks回撥列表中放入一個回撥函式,回撥函式負責執行f1並且更新p2的狀態.
- 然後.then(f2)時返回了一個bridgePromise(p3),這裡注意其實是p2.then(f2),因為p.then(f1)時返回了p2。此時在p2的onFulfilledCallbacks回撥列表中放入一個回撥函式,回撥函式負責執行f2並且更新p3的狀態.
- 然後.then(f3)時返回了一個bridgePromise(p4),並在p3的onFulfilledCallbacks回撥列表中放入一個回撥函式,回撥函式負責執行f3並且更新p4的狀態. 到此,回撥關係註冊完了,如圖所示:
- 然後過了一段時間,p裡面的非同步操作執行完了,讀取到了1.txt的內容,開始執行p的回撥函式,回撥函式執行f1,列印出1.txt的內容“this is 1.txt”,並將f1的返回值放到resolvePromise中開始解析。resolvePromise一看傳入了一個promise物件,promise是非同步的啊,得等著呢,於是就在這個promise物件的then方法中繼續resolvePromise這個promise物件resolve的結果,一看不是promise物件了,而是一個具體值“this is 2.txt”,於是呼叫bridgePromise(p2)的reslove方法將bridgePromise(p2)的狀態更新為fulfilled,並將“this is 2.txt”傳入p2的回撥函式中去執行。
- p2的回撥開始執行,f2拿到傳過來的“this is 2.txt”引數開始執行,列印出2.txt的內容,並將f2的返回值放到resolvePromise中開始解析,resolvePromise一看傳入了一個promise物件,promise是非同步的啊,又得等著呢........後續操作就是不斷的重複4,5步直到結束。
到此,reslove這一條線已經我們已經走通,讓我們看看reject這一條線,reject其實處理起來很簡單:
- 首先執行fn及執行註冊的回撥時都用try-catch包裹,無論哪裡有異常都會進入reject分支。
- 一旦程式碼進入reject分支直接將bridge promise設為rejected狀態,於是後續都會走reject這個分支,另外如果不傳異常處理的onRejected函式,預設就是使用throw error將錯誤一直往後拋,達到了錯誤冒泡的目的。
- 最後可以實現一個catch函式用來接收錯誤。
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
複製程式碼
到此,我們已經可以愉快的使用promise.then(f1).then(f2).then(f3).catch(errorLog)
來順序讀取檔案內容了。
本節程式碼
六. 達到Promises/A+規範
其實,到支援序列非同步任務這一節,我們寫的promise在功能上已經基本齊全了,但是還不太規範,比如說一些其他情況的判斷等等,這一節我們就比著Promises/A+的規範打磨一下我們寫的promise。如果只是想學習promise的核心實現的,這一節看不懂也沒關係,因為這一節並沒有增加promise的功能,只是使promise更加規範,更加健壯。
目標
使promise達到Promises/A+規範,通過promises-aplus-tests的完整測試
實現
首先來可以瞭解一下Promises/A+規範:
Promises/A+規範原版
Promises/A+規範中文版
相比上一節程式碼,本節程式碼除了在resolvePromise函式裡增加了幾個其他情況的判斷外,其他函式都沒有修改。完整promise程式碼如下:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function MyPromise(fn) {
const self = this;
self.value = null;
self.error = null;
self.status = PENDING;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value) {
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
if (self.status === PENDING) {
setTimeout(() => {
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
}, 0)
}
}
function reject(error) {
if (self.status === PENDING) {
setTimeout(function() {
self.status = REJECTED;
self.error = error;
self.onRejectedCallbacks.forEach((callback) => callback(self.error));
}, 0)
}
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
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);
}
}
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);
}
}, 0);
})
}
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);
}
}, 0);
});
}
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);
}
});
});
}
}
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
// 執行測試用例需要用到的程式碼
MyPromise.deferred = function() {
let defer = {};
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
try {
module.exports = MyPromise
} catch (e) {}
複製程式碼
我們可以先跑一下測試,需要安裝一下測試外掛,然後執行測試,測試時注意在加上上面最後的那幾行程式碼才能執行測試用例。
1.npm i -g promises-aplus-tests
2.promises-aplus-tests mypromise.js
複製程式碼
執行測試用例可以看到,我們上面寫的promise程式碼通過了完整的Promises/A+規範測試。
然後開始分析我們這一節的程式碼,我們主要在resolvePromise里加了額外的兩個判斷,第一個是x和bridgePromise是指向相同值時,報出迴圈引用的錯誤,使promise符合2.3.1規範,然後我們增加了一個x 為物件或者函式的判斷,這一條判斷主要對應2.3.3規範,中文規範如圖:
這一條標準對應的其實是thenable物件,什麼是thenable物件,只要有then方法就是thenable物件,然後我們實現的時候照著規範實現就可以了。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);
}
}
複製程式碼
再寫完這個分支的程式碼後,其實我們已經可以刪除if (x instanceof MyPromise) {}
這個分支的程式碼,因為promise也是一個thenable物件,完全可以使用上述程式碼相容代替。另外,本節程式碼很多重複程式碼可以封裝優化一下,但是為了看得清晰,並沒有進行抽象封裝,大家如果覺得重複程式碼太多的話,可以自行抽象封裝。
本節程式碼
七. 實現 promise 的all,race,resolve,reject方法
上一節我們已經實現了一個符合Promises/A+規範的promise,本節我們把一些es6 promise裡的常用方法實現一下。
目標
實現es6 promise的all,race,resolve,reject方法
實現
我們還是在之前的基礎上繼續往下寫:
MyPromise.all = function(promises) {
return new MyPromise(function(resolve, reject) {
let result = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(function(data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, function(error) {
reject(error);
});
}
});
}
MyPromise.race = function(promises) {
return new MyPromise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(function(data) {
resolve(data);
}, function(error) {
reject(error);
});
}
});
}
MyPromise.resolve = function(value) {
return new MyPromise(resolve => {
resolve(value);
});
}
MyPromise.reject = function(error) {
return new MyPromise((resolve, reject) => {
reject(error);
});
}
複製程式碼
其實前幾節把promise的主線邏輯實現後,這些方法都不難實現,all的原理就是返回一個promise,在這個promise中給所有傳入的promise的then方法中都註冊上回撥,回撥成功了就把值放到結果陣列中,所有回撥都成功了就讓返回的這個promise去reslove,把結果陣列返回出去,race和all大同小異,只不過它不會等所有promise都成功,而是誰快就把誰返回出去,resolve和reject的邏輯也很簡單,看一下就明白了。
本節程式碼
實現all,race,resolve,reject方法程式碼
八. 實現 promiseify 方法
其實到上一節為止,promise的方法已經都講完了,這一節講一個著名promise庫bluebird裡面的方法promiseify,因為這個方法很常用而且以前面試還被問過。promiseify有什麼作用呢?它的作用就是將非同步回撥函式api轉換為promise形式,比如下面這個,對fs.readFile 執行promiseify後,就可以直接用promise的方式去呼叫讀取檔案的方法了,是不是很強大。
let Promise = require('./bluebird');
let fs = require("fs");
var readFile = Promise.promisify(fs.readFile);
readFile("1.txt", "utf8").then(function(data) {
console.log(data);
})
複製程式碼
目標
實現bluebird的promiseify方法
實現
MyPromise.promisify = function(fn) {
return function() {
var args = Array.from(arguments);
return new MyPromise(function(resolve, reject) {
fn.apply(null, args.concat(function(err) {
err ? reject(err) : resolve(arguments[1])
}));
})
}
}
複製程式碼
雖然方法很強大,但是實現起來並沒有很難,想在外邊直接呼叫promise的方法那就返回一個promise唄,內部將原來引數後面拼接一個回撥函式引數,在回撥函式裡執行這個promise的reslove方法把結果傳出去,promiseify就實現了。
本節程式碼
最後
不知不覺寫了這麼多了,大家如果覺得還可以就給個讚唄,另外每一節的程式碼都託管到了github上,大家可以對照看那一節的promise實現程式碼及測試程式碼,也順便求個star~
專案地址:本文程式碼的github倉庫
另外,實現一個符合Promises/A+規範的promise不止本文一種實現方式,本文只是選取了一種比較通俗易懂的實現方式作為講解,大家也可以用自己的方式去實現一個符合Promises/A+規範的promise。