寫這篇的文章的緣由是在公司內部的前端小組裏面分享了一下關於Promise的實現。感受內容還不錯,因此在這裏分享給你們。源碼文件會放到Github上面,感興趣的同窗能夠去查看源碼。javascript
Promise
的核心思想是Promise
表示異步操做的結果。一個Promise
處於如下三種狀態之一:前端
pending
- Promise
的初始化狀態fulfilled
- 表示 Promise
成功操做的狀態rejected
- 表示 Promise
錯誤操做的狀態Promise
的內部狀態改變如圖所示:java
在Promise
沒有出現以前,咱們會看到不少相似的代碼。git
const fs = require('fs')
fs.readFile('./data.txt','utf8',function(err,data){
fs.readFile(data, 'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data);
})
})
})
複製代碼
Promise
出現以後,就能夠採用鏈式調用的形式來寫。github
const fs = require('fs')
const readFile = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) reject(err);
resolve(data);
})
})
}
readFile('./data.txt').then((data) => {
return readFile(data)
}).then((data) => {
return readFile(data)
}).then((data) => {
console.log(data);
})
複製代碼
使用了Promise
以後代碼風格變得優雅了不少,寫法上也更加直觀。typescript
Promise.all
的出現讓咱們能夠更加方便的處理多個任務完成時在進行處理的邏輯。json
Promise
1.在動手寫代碼以前先了解一下須要實現哪些功能。promise
Promise constructor
new Promise
時,構造函數須要傳入一個executor()
執行器,executor
函數會當即執行,而且它支持傳入兩個參數,分別是resolve
和reject
。markdownclass Promise<T> { constructor(executor: (resolve: (value: T ) => void, reject: >(reason?: any) => void) => void){ } } 複製代碼
Promise
狀態 「Promise/A+ 2.1」
Promise
必須處於如下三種狀態之一:併發
pending
(等待中),能夠轉換爲fulfilled
(完成)或rejected
(拒絕)。當狀態從
pending
切換到fulfilled
時,該狀態不得再過渡到其它狀態,而且必須具備一個值,該值不能更改。當狀態從
pending
切換到rejected
時,該狀態不得再過渡到其它狀態,而且必須有一個失敗的緣由,不能更改。
Promise then
方法 「Promise/A+ 2.2」
Promise
必須有一個then
方法,then
接收兩個參數,分別是成功時的回調onFulfilled
, 和失敗時的回調onRejected
。
onFulfilled
和onRejected
是可選的參數,而且若是傳入的onFulfilled
和onRejected
不是函數的話,則必須將其忽略。若是
onfulfilled
是一個函數。則它必須在Promise
的狀態變成fulfilled
(完成)時才能調用,Promise
的值是傳進它的第一個參數。而且它只能被調用一次。若是
onRejected
是一個函數,則它必須在Promise
的狀態爲 rejected(失敗)時調用,並把失敗的緣由傳入它的第一個參數。只能被調用一次。
既然知道了須要實現那些功能,那就來動手操做一下,代碼以下:
// 使用枚舉定義Promise的狀態
enum PROMISE_STATUS {
PENDING,
FULFILLED,
REJECTED
}
class _Promise<T> {
// 保存當前狀態
private status = PROMISE_STATUS.PENDING
// 保存resolve的值,或者reject的緣由
private value: T
constructor(executor: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
executor(this._resolve, this._reject)
}
// 根據規範完成簡易功能的then方法
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
if (this.status === PROMISE_STATUS.FULFILLED) {
// 狀態爲fulfilled時調用成功的回調函數
onfulfilled(this.value)
}
if (this.status === PROMISE_STATUS.REJECTED) {
// 狀態爲rejected時調用失敗的回調函數
onrejected(this.value)
}
}
// 傳入executor方法的第一個參數,調用此方法就是成功
private _resolve = (value) => {
if (value === this) {
throw new TypeError('A promise cannot be resolved with itself.');
}
// 只有是pending狀態才能夠更新狀態,防止二次調用
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.FULFILLED;
this.value = value;
}
// 傳入executor方法的第二個參數,調用此方法就是失敗
private _reject = (value) => {
// 只有是pending狀態才能夠更新狀態,防止二次調用
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.REJECTED;
this.value = value
}
}
複製代碼
代碼寫完了咱們測試一下功能:
const p1 = new _Promise((resolve, reject) => {
resolve(2)
})
p1.then(res => {
console.log(res, 'then ok1')
})
const p2 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000);
})
p2.then(res => {
console.log(res, 'then ok2')
})
複製代碼
控制檯會打印出:
2 "then ok1"
複製代碼
不錯,如今已是稍見雛形。
咱們已經實現了一個入門級的 Promise
,可是細心的同窗應該已經發現了,then ok2
這個值沒有打印出來。
致使這個問題出現的緣由是什麼呢?原來是咱們在執行then函數的時候,因爲是異步操做,狀態一直處於pending的狀態,傳進來的回調函數沒有觸發執行。
知道了問題就好解決了。只須要把傳進來的回調函數存儲起來。在調用resolve或reject方法的時候執行就能夠了,咱們優化一下代碼:
enum PROMISE_STATUS {
PENDING,
FULFILLED,
REJECTED
}
class _Promise<T> {
private status = PROMISE_STATUS.PENDING
private value: T
// 保存then方法傳入的回調函數
private callbacks = []
constructor(executor: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
executor(this._resolve, this._reject)
}
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
// 把then方法傳入的回調函數整合一下
const handle = () => {
if (this.status === PROMISE_STATUS.FULFILLED) {
onfulfilled && onfulfilled(this.value)
}
if (this.status === PROMISE_STATUS.REJECTED) {
onrejected && onrejected(this.value)
}
}
if (this.status === PROMISE_STATUS.PENDING) {
// 當狀態是pending時,把回調函數保存進callback裏面
this.callbacks.push(handle)
}
handle()
}
private _resolve = (value) => {
if (value === this) {
throw new TypeError('A promise cannot be resolved with itself.');
}
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.FULFILLED;
this.value = value;
// 遍歷執行回調
this.callbacks.forEach(fn => fn())
}
private _reject = (value) => {
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.REJECTED;
this.value = value
// 遍歷執行回調
this.callbacks.forEach(fn => fn())
}
}
複製代碼
在來測試一下上面的代碼:
const p2 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000);
})
p2.then(res => {
console.log(res, 'then ok2')
})
複製代碼
在等待1s後,控制檯會打印出:
2 "then ok2"
複製代碼
目前已經能夠支持異步操做了。如今的你已是江湖中的高手了。
在文章一開頭介紹Promise時,提到了鏈式調用的概念.then().then()
,如今就要實現這個相當重要的功能,在開始前先看一下Promise/A+
的規範
then
必須返回一個Promise 「Promise/A+ 2.2.7」
promise2 = promise1.then(onFulfilled, onRejected);
若是一個
onFulfilled
或onRejected
返回一個值x
,則運行Promise Resolution Procedure
(會在下面實現這個方法)。若是任何一個
onFulfilled
或onRejected
引起異常e
則promise2
必須以e
爲其理由reject
(拒絕).若是
onFulfilled
不是函數且promise1
狀態已經fuifilled
(完成),則promise2
必須使用與相同的值來實現promise1
。若是
onRejected
不是函數而promise1
狀態爲rejected
(拒絕),則promise2
必須以與相同的理由將其拒絕promise1
。Promise Resolution Procedure 實現
首先該方法的使用方式相似於下面這種形式
resolvePromise(promise,x,...)
若是
promise
和x
引用相同的對象,promise 則應該以TypeError爲理由拒絕。「Promise/A+ 2.3.1」若是
x
是一個promise
,則應該採用它本來的狀態返回。「Promise/A+ 2.3.2」不然,判斷
x
若是是對象或者是函數。則執行如下操做,先聲明let then = x.then
,若是出現異常結果e
,則以e
做爲promise
reject(拒絕)的緣由。若是then
是個函數,則用call
執行then
,把this
指向爲x
,第一個參數用resolvePromise
調用,第二個用rejectPromise
調用「Promise/A+ 2.3.3」若是
x
不是對象或者方法,則使用x
的值resolve
完成 「Promise/A+ 2.3.4」
只是經過文字不太容易理解,咱們來看一下代碼的實現:
enum PROMISE_STATUS {
PENDING,
FULFILLED,
REJECTED
}
class _Promise<T> {
private status = PROMISE_STATUS.PENDING
private value: T
private callbacks = []
constructor(executor: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
executor(this._resolve, this._reject)
}
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
const nextPromise = new _Promise((nextResolve, nextReject) => {
const handle = () => {
if (this.status === PROMISE_STATUS.FULFILLED) {
const x = (onfulfilled && onfulfilled(this.value))
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
}
if (this.status === PROMISE_STATUS.REJECTED) {
if (onrejected) {
const x = onrejected(this.value)
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
} else {
nextReject(this.value)
}
}
}
if (this.status === PROMISE_STATUS.PENDING) {
this.callbacks.push(handle)
} else {
handle()
}
});
return nextPromise
}
private _resolve = (value) => {
if (value === this) {
throw new TypeError('A promise cannot be resolved with itself.');
}
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.FULFILLED;
this.value = value;
this.callbacks.forEach(fn => fn())
}
private _reject = (value) => {
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.REJECTED;
this.value = value
this.callbacks.forEach(fn => fn())
}
private _resolvePromise = <T>(nextPromise: _Promise<T>, x: any, resolve, reject) => {
// 2.3.1 nextPromise 不能和 x 相等
if (nextPromise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
// 2.3.2 若是 x 是 Promise 返回 x 的狀態和值
if (x instanceof _Promise) {
x.then(resolve, reject)
}
// 2.3.3 若是 x 是對象或者函數執行 if 裏面的邏輯
if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x);
}
// 2.3.3.1
let then;
try {
then = x.then;
} catch (error) {
return reject(error);
}
// 2.3.3.3
if (typeof then === 'function') {
// 聲明called 在調用過一次resolve或者reject以後,修改成true,保證只能調用一次
let called = false;
try {
then.call(x, y => {
if (called) return; // 2.3.3.3.4.1
called = true;
// 遞歸解析的過程(由於可能 promise 中還有 promise)
this._resolvePromise(nextPromise, y, resolve, reject)
}, r => {
if (called) return; // 2.3.3.3.4.1
called = true;
reject(r)
})
} catch (e) {
if (called) return; // 2.3.3.3.4.1
// 2.3.3.3.4
reject(e)
}
} else {
// 2.3.3.4
resolve(x)
}
} else {
// 2.3.4
resolve(x);
}
}
}
複製代碼
目前已經實現能夠鏈式調用的功能了,咱們來測試一下:
const p3 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 1000);
})
p3.then(res => {
console.log(res, 'then ok3')
return '鏈式調用'
}).then(res => {
console.log(res)
})
複製代碼
等待1s以後,控制檯會打印出:
3 "then ok3"
"鏈式調用"
複製代碼
有沒有同窗想到還缺乏了一個尤其重要的功能,那就是微任務。咱們應該如何實現和內置 Promise
同樣的微任務流程呢?
在 Web Api
裏面有這樣一個方法 MutationObserver。咱們能夠基於它實現微任務的功能。而且也已經有相關的庫給咱們封裝好了這個方法,它就是 asap。只要把須要以微任務執行的函數傳入便可。
asap(function () {
// ...
});
複製代碼
其實在 Web Api
裏面還有這樣一個方法 queueMicrotask 能夠直接使用。使用方式也是把要以微任務執行的函數傳入進去便可。
self.queueMicrotask(() => {
// 函數的內容
})
複製代碼
queueMicrotask
惟一的缺點就是兼容性不太好,在生產環境中建議仍是使用 asap
這個庫來實現微任務。
把以前寫好的 Promise then
方法稍微作一下調整:
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
const nextPromise = new _Promise((nextResolve, nextReject) => {
const _handle = () => {
if (this.status === PROMISE_STATUS.FULFILLED) {
const x = (onfulfilled && onfulfilled(this.value))
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
}
if (this.status === PROMISE_STATUS.REJECTED) {
if (onrejected) {
const x = onrejected(this.value)
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
} else {
nextReject(this.value)
}
}
}
const handle = () => {
// 支持微任務
queueMicrotask(_handle)
}
if (this.status === PROMISE_STATUS.PENDING) {
this.callbacks.push(handle)
} else {
handle()
}
});
return nextPromise
}
複製代碼
如今完美支持微任務,和內置 Promises
事件執行順序一致。咱們來測試一下:
console.log('first')
const p1 = new _Promise(function (resolve) {
console.log('second')
resolve('third')
})
p1.then(console.log)
console.log('fourth')
複製代碼
能夠看到控制檯打印的結果爲:
first
second
fourth
third
複製代碼
到這裏,咱們已經把Promise最關鍵的功能完成了:支持異步操做
,then支持鏈式調用
,支持微任務
。
1.下載Promise/A+規範提供了一個專門的測試腳本 promises-aplus-tests
yarn add promises-aplus-tests -D
複製代碼
2.在咱們的代碼中加入如下代碼:
(_Promise as any).deferred = function () {
let dfd = {} as any;
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = _Promise;
複製代碼
3.修改 package.json
文件增長如下內容(./dist/promise/index.js
是須要測試的文件路徑):
{
"scripts": {
"test": "promises-aplus-tests ./dist/promise/index.js"
}
}
複製代碼
4.執行 yarn test
能夠看到872個測試用例所有經過。
Promise
的其它 API
實現到目前爲止,上述代碼已經完整的按照 Promise/A+ 規範實現了,但還有一些內置Api沒有實現。下面就把這些內置的方法來實現:
class _Promise {
catch(onrejected) {
return this.then(null, onrejected)
}
finally(cb) {
return this.then(
value => _Promise.resolve(cb()).then(() => value),
reason => _Promise.resolve(cb()).then(() => { throw reason })
);
}
static resolve(value) {
if (value instanceof _Promise) {
return value;
}
return new _Promise(resolve => {
resolve(value);
});
}
static reject(reason) {
return new _Promise((resolve, reject) => {
reject(reason);
});
}
static race(promises) {
return new _Promise(function (resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError('Promise.race accepts an array'));
}
for (var i = 0, len = promises.length; i < len; i++) {
_Promise.resolve(promises[i]).then(resolve, reject);
}
});
}
static all(promises) {
let result = [];
let i = 0;
return new _Promise((resolve, reject) => {
const processValue = (index, value) => {
result[index] = value;
i++;
if (i == promises.length) {
resolve(result);
};
};
for (let index = 0; index < promises.length; index++) {
promises[index].then(value => {
processValue(index, value);
}, reject);
};
});
}
static allSettled(promises) {
let result = []
let i = 0;
return new _Promise((resolve, reject) => {
const processValue = (index, value, status: 'fulfilled' | 'rejected') => {
result[index] = { status, value }
i++;
if (i == promises.length) {
resolve(result);
};
};
for (let index = 0; index < promises.length; index++) {
promises[index].then(value => {
processValue(index, value, 'fulfilled')
}, value => {
processValue(index, value, 'rejected')
});
};
})
}
...
}
複製代碼
async
和 await
咱們完成了 Promise
的實現。可是你們有沒有想過 async
和 await
這個 Promise
的語法糖如何實現呢?
這裏咱們就要藉助 Generator
函數來實現這個功能,廢話少說,直接上代碼:
let gp1 = new _Promise(r => {
setTimeout(() => {
r(1)
}, 1000);
})
let gp2 = new _Promise(r => {
setTimeout(() => {
r(2)
}, 1000);
})
function* gen() {
let a = yield gp1
let b = yield gp2
return b + a
}
function run(gen) {
return new _Promise(function (resolve, reject) {
g = gen()
function next(v) {
ret = g.next(v)
if (ret.done) return resolve(ret.value);
_Promise.resolve(ret.value).then(next)
}
next()
})
}
run(gen).then(console.log)
複製代碼
控制檯裏面會打印出結果爲:3
若是對這個 run
函數感興趣,推薦去看下這個 co 庫實現,代碼寫的很是簡潔,只有一百行左右,值的一看。
用了近半天的時間才把這篇文章給寫出來。其中的源碼文件已經放到 Github 上面。不想手敲一遍的同窗能夠直接拉下來代碼執行查看結果。若是你有不一樣的意見或想法也歡迎留言。
相關資源連接