今天的任務是根據Promise A+
規範簡單實現一個 Promise
庫。開始之前,我們可以先通讀一下這個規範,戳這裡。
靜心讀一下這個規範真的很重要,很重要,很重要。雖然是英文版,但是讀起來也許會比漢語更能讓人理解。
A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.
提到 Promise
自然想到非同步,非同步想到回撥,回撥就是回頭在調。回頭在調的前提是提前儲存回撥內容,等到時間後從儲存的地方取到對應的內容再執行即可。
一、Promise 建構函式實現
2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
2.1.1 A pending promise may transition to either the fulfilled or rejected state.
2.1.2 A fulfilled promise must have a value, which must not change.
2.1.3 A rejected promise must have a reason, which must not change.
我們平時但凡做什麼事情,大概都有三種基本狀態,正在進行、成功、失敗。一個 Promise
代表一個非同步操作,也有這三種狀態,且狀態只能從正在進行到成功或者失敗,不可逆轉。不管事情最終什麼狀態,既然結果出來了,那就通知之前快取回撥的陣列(因為可以 then
多次,故為陣列)並把結果給它吧。
Promise
基本使用:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`success`)
}, 1000)
})
promise.then((data) => {console.log(data)})
複製程式碼
首先 Promise
是一個類,通過 new
建立一個例項,引數是一個函式,函式引數預設命名為 resolve
和 reject
,我們稱該函式為一個執行器 executor
,預設執行。構造函中還需要一個狀態變數 status
,儲存當前執行狀態;以及執行成功 value
或失敗 reason
的結果變數;最後還需要上面提及的快取回撥的陣列。實現程式碼如下:
class Promise () {
constructor (executor) {
// 預設等待態
this.status = `pending`
// 成功結果
this.value = undefined
// 失敗原因
this.reason = undefined
// 儲存成功回撥陣列
this.onResolvedCallbacks = []
// 儲存失敗回撥陣列
this.onRejectedCallbacks = []
// 執行器
executor(resolve, reject)
}
}
複製程式碼
執行器函式引數是 resolve
和 reject
, 同樣也是函式,功能是有結果後觸發快取回撥陣列,也可以理解為釋出。實現程式碼如下:
class Promise () {
constructor (executor) {
// 變數宣告
this.status = `pending`
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
// 結果成功,通知函式定義
let resolve = (value) => {
if (this.status === `pending`) {
this.status = `resolved`
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
// 結果失敗,通知函式定義
let reject = (reason) => {
if (this.status === `pending`) {
this.status = `rejected`
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
}
複製程式碼
既然有結果後要觸發快取回撥陣列,那麼接下來我們看一下這個回撥陣列如何快取。
二、Promise.then 實現
2.2 A promise must provide a then method to access its current or eventual value or reason.
1、then 訂閱功能
我們都知道,promise
例項只有通過呼叫 then
函式才能拿到結果,then
函式引數分別為成功回撥和失敗回撥,回撥引數為成功值和失敗值。固 then
函式的主要功能即是快取回撥函式,也可以理解為訂閱。實現程式碼如下:
class Promise () {
// constructor省略...
then (onFulfilled, onRejected) {
// 如果exector內容為同步程式碼,resolve函式執行後,狀態已變為resolved,則直接執行回撥
if (this.status === `resolved`) {
onFulfilled(this.value)
}
// 如果exector內容為同步程式碼,reject函式執行後,狀態已變為rejected,直接執行回撥
if (this.status === `rejected`) {
onRejected(this.reason)
}
// 如果exector內容為非同步程式碼,狀態為pending時,則快取回撥函式,我們也可以理解為訂閱
if (this.status === `pending`) {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
複製程式碼
2、then 返回 promise
2.2.6 Then may be called multiple times on the same promise.
2.2.7 Then must return a promise. promise2 = promise1.then(onFulfilled, onRejected)
實現程式碼如下:
class Promise () {
// constructor省略...
then (onFulfilled, onRejected) {
let promise2
if (this.status === `resolved`) {
promise2 = new Promise((resolve, reject) => {
onFulfilled(this.value)
})
}
if (this.status === `rejected`) {
promise2 = new Promise((resolve, reject) => {
onRejected(this.reason)
})
}
if (this.status === `pending`) {
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
})
}
return promise2
}
}
複製程式碼
2、then 呼叫多次
2.2.7.1 onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
then
可以呼叫多次,且每次 then
引數 onFulFilled
函式的引數值為上一次 promise
函式的執行結果,即為 onFulfilled(this.value)
的執行結果,我們設為 x
。根據 x
的不同情況做不同處理,詳情見 Promise A+
規範 2.3 Promise Resolution Procedure
。
實現程式碼如下:
class Promise () {
// constructor省略...
then (onFulfilled, onRejected) {
let promise2
if (this.status === `resolved`) {
promise2 = new Promise((resolve, reject) => {
let x = onFulfilled(this.value)
// Promise Resolution Procedure
resolvePromise(promise2, x, resolve. reject)
})
}
if (this.status === `rejected`) {
promise2 = new Promise((resolve, reject) => {
let x = onRejected(this.reason)
// Promise Resolution Procedure
resolvePromise(promise2, x, resolve. reject)
})
}
if (this.status === `pending`) {
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(() => {
let x = onFulfilled(this.value)
// Promise Resolution Procedure
resolvePromise(promise2, x, resolve. reject)
})
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
})
})
}
return promise2
}
}
// 處理函式:處理promise2和x的關係
function resolvePromise (promise2, x, resolve, reject) {
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise2 === x) {
return reject(new TypeError(`迴圈引用`))
}
// 2.3.3 if x is an object or function
if (x !== null && typeof x === `object` || typeof x === `function` ){
try {
// 2.3.3.1 Let then be x.then.
let then = x.then
// 2.3.3.3 If then is a function,call it with x as this.
if (typeof then === `function`) {
// 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
then.call(x, y => {
resolvePromise(promise2, y, resolve, reject)
}, r => {
// 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
reject(r)
})
}
} catch (e) {
// 2.3.3.4 If calling then throws an exception e
reject(e)
}
} else {
// 2.3.4 If x is not an object or function, fulfill promise with x.
resolve(x)
}
}
複製程式碼
3、狀態不可逆轉
實現程式碼如下:
// 處理函式:處理promise2和x的關係
function resolvePromise (promise2, x, resolve, reject) {
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise2 === x) {
return reject(new TypeError(`迴圈引用`))
}
let called
// 2.3.3 if x is an object or function
if (x !== null && typeof x === `object` || typeof x === `function` ){
try {
// 2.3.3.1 Let then be x.then.
let then = x.then
// 2.3.3.3 If then is a function,call it with x as this.
if (typeof then === `function`) {
// 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
then.call(x, y => {
// `2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.`
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, r => {
if (called) return
called = true
// 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
reject(r)
})
}
} catch (e) {
// `2.3.3.3.4 If calling then throws an exception e,2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.`
if (called) return
called = true
// 2.3.3.4 If calling then throws an exception e
reject(e)
}
} else {
// 2.3.4 If x is not an object or function, fulfill promise with x.
resolve(x)
}
}
複製程式碼
4、Promise.then 非同步執行
實現程式碼如下:
class Promise () {
then (onFulfilled, onRejected) {
let promise2
if (this.status === `resolved`) {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
if (this.status === `rejected`) {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}. 0)
})
}
if (this.status === `pending`) {
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
promiseResolve(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
promiseResolve(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
})
}
return promise2
}
}
複製程式碼
5、Promise.then 穿透
promise.then().then()
多次,都可以獲取到最終結果,固當 then
引數 onFulfilled
為空時,需要自動 return value
;當 onRejected
為空時,需要 return throw err
。實現程式碼如下:
class Promise () {
then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === `function` ? onFulfilled : (value) => { return value }
onRejected = typeof onRejected === `function` ? onRejected : (err) => { throw err }
// promise2省略...
}
}
複製程式碼
三、其它方法實現
1、Promise.resolve
實現程式碼如下:
Promise.resolve = function (val) {
return new Promise((resolve, reject) => {
resolve(val)
})
}
複製程式碼
2、Promise.reject
實現程式碼如下:
Promise.reject = function (val) {
return new Promise((resolve, reject) => {
reject(val)
})
}
複製程式碼
3、Promise.all
實現程式碼如下:
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let dataArr = []
let count = 0
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
dataArr[i] = data
count++
if (count === promises.lenth) {
resolve(dataArr)
}
}, reject)
}
})
}
複製程式碼
4、Promise.race
實現程式碼如下:
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promise[i].then(resolve, reject)
}
})
}
複製程式碼
5、Promise.promisify
實現程式碼如下:
Promise.promisify = function (fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
}
複製程式碼
四、Promise 校驗
要想驗證自己實現的 Promise
是否符合 Promise A+
規範,可以全域性安裝 promises-aplus-tests
工具包。
Adapters
:
In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js modules with a few well-known exports:
resolved(value): creates a promise that is resolved with value.
rejected(reason): creates a promise that is already rejected with reason.
deferred(): creates an object consisting of { promise, resolve, reject }
The resolved and rejected exports are actually optional, and will be automatically created by the test runner using deferred
resolved
和 rejected
是可選的,下面我們實現一下 deferred
,實現程式碼如下:
Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
複製程式碼
到這裡一切準備就緒,校驗步驟如下:
npm install promises-aplus-tests -g
promises-aplus-tests ./Promise.js
複製程式碼
好了,到這裡全部程式碼都已呈現,趕快自己親手試一下吧!???