這是我參與更文挑戰的第5天,活動詳情查看: 更文挑戰javascript
想要實現 Promise,必須先了解 Promise 是什麼,以及 Promise 有哪些功能。java
還不是特別瞭解 Promise 的同窗,建議先移步 ES6入門-Promise 熟悉。git
Promise 是基於 Promises/A+ 規範 實現的,換句話說,咱們能夠按照 Promises/A+ 規範 來手寫 Promise。es6
Promise,直譯過來就是承諾,Promise 到底承諾了什麼呢?github
當我在麥當勞點一份漢堡套餐,收銀員會給我一張收據,這個收據就是 Promise,表明我已經付過錢了,麥當勞會爲我作一個漢堡套餐的承諾,我要經過收據來取這個漢堡套餐。web
那麼這個買漢堡獲得的承諾會有如下 3 種狀態:面試
須要注意的是,狀態的修改是不可逆的,當漢堡作好了,承諾兌現了,就不能再回到等待狀態了。npm
總結一下,Promise 就是一個承諾,承諾會給你一個處理結果,多是成功的,多是失敗的,而返回結果以前,你能夠同時作其餘事情。數組
接下來,按照 Promises/A+ 規範 一步步實現 Promise。promise
先瞅一眼 ES6 Promise 基本用法。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(data => {
console.log('請求成功')
}, err => {
console.log('請求失敗')
})
複製代碼
Promise 擁有本身的狀態,初始狀態->成功狀態時,執行成功回調,初始狀態->失敗狀態時,執行失敗回調。
經過已知的 Promise 3 種狀態,可定義常量 STATUS 和 MyPromise 狀態 status。
代碼以下:
// Promise 3 種狀態
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
class MyPromise {
// 初始狀態爲 pending
status = STATUS.PENDING
}
複製代碼
從基本用法可知,Promise 須要接收 1 個執行器函數做爲參數,這個函數帶有 2 個參數。
代碼以下:
class MyPromise {
constructor (executor) {
// 執行器
executor(this.resolve, this.reject)
}
// 成功返回值
value = null
// 失敗返回值
reason = null
// 修改 Promise 狀態,並定義成功返回值
resolve = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
}
}
// 修改 Promise 狀態,並定義失敗返回值
reject = () => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = value
}
}
}
}
複製代碼
Promise 擁有 then 方法,then 方法第一個參數是成功狀態的回調函數 onFulfilled,第二個參數是失敗狀態的回調函數 onRejected。
promise.then(onFulfilled, onRejected)
複製代碼
onFulfilled 要求以下:
onRejected 要求以下:
代碼以下:
class MyPromise {
then = function (onFulfilled, onRejected) {
if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
} else if (this.status === STATUS.REJECTED) {
onRejected(this.reason)
}
}
}
複製代碼
按照 Promise 的基本用法,建立 MyPromise 實例 mypromise。
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
mypromise.then(data => {
console.log(data, '請求成功') // 成功打印「成功 請求成功」
}, err => {
console.log(err, '請求失敗')
})
複製代碼
試行成功,打印結果爲「成功 請求成功」。
源碼地址:基本用法源碼
下文將按照 Promises/A+ 規範 完善 MyPromise.then 方法。
Promises/A+ 規範 中標明 then 有如下要求:
onFulfilled、onRejected 是可選參數。
代碼以下:
class MyPromise {
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
}
}
複製代碼
then 能夠在同一個承諾上屢次調用。
能夠理解爲將 onFulfilled、onRejected 做爲數組存儲在 MyPromise 中,而後按照順序執行。
代碼以下:
class MyPromise {
// 成功回調
onFulfilledCallback = []
// 失敗回調
onRejectedCallback = []
// 修改 Promise 狀態,並定義成功返回值
resolve = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
while(this.onFulfilledCallback.length) {
this.onFulfilledCallback.shift()(value)
}
}
}
// 修改 Promise 狀態,並定義失敗返回值
reject = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = value
while(this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(value)
}
}
}
then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if (this.status === STATUS.PENDING) {
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.push(onRejected)
} else if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
} else if (this.status === STATUS.REJECTED) {
onRejected(this.reason)
}
}
}
複製代碼
由此,咱們已實現了一個基礎的 Promise。
看了這麼久,試一試 MyPromise 是否符合要求吧。
代碼以下:
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
mypromise.then(data => {
console.log(data, '1')
})
mypromise.then(data => {
console.log(data, '2')
})
複製代碼
輸出結果如圖:
由圖可知,和預期同樣。
源碼地址:屢次調用then 源碼
then 必須返回一個 Promise 來支持鏈式調用 Promise。
示例代碼以下:
mypromise.then(data => {
console.log(data, '請求成功')
return '2'
}).then(data => {
console.log(data, '請求成功')
return '3'
})
複製代碼
改動點以下:
class MyPromise {
then = function (onFulfilled, onRejected) {
// 返回 MyPromise實例
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === STATUS.PENDING) {
this.onFulfilledCallback.push(() => {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
})
this.onRejectedCallback.push(() => {
const x = onRejected(this.value)
resolvePromise(promise2, x, resolve, reject)
})
} else if (this.status === STATUS.FULFILLED) {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} else if (this.status === STATUS.REJECTED) {
const x = onRejected(this.error)
resolvePromise(promise2, x, resolve, reject)
}
})
return promise2
}
}
複製代碼
上述代碼引用了 resolvePromise 來處理 Promise.then 的返回值,
Promises/A+ 規範 對resolvePromise 的要求以下:
代碼以下:
function resolvePromise (promise2, x, resolve, reject) {
// 若是 promise2 === x, 執行 reject,錯誤緣由爲 TypeError
if (promise2 === x) {
reject(new TypeError('The promise and the return value are the same'))
}
// 若是 x 是函數或對象
if (typeof x === 'object' || typeof x === 'function') {
let then
try {
then = x.then
} catch (error) {
reject(error)
}
// 若是 x.then 是函數
if (typeof then === 'function') {
then.call(x, y => {
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
reject(err);// 失敗了
})
} else {
// 若是 x.then 不是函數
resolve(x)
}
} else {
// 若是 x 不是 promise 實例
resolve(x)
}
}
複製代碼
試試看能不能符合預期,鏈式調用 then 吧。
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
const mypromise2 = new MyPromise((resolve, reject) => {
resolve('成功2')
})
mypromise.then(data => {
console.log(data, '1')
return mypromise2
}).then(data => {
console.log(data, '2')
})
複製代碼
輸出結果爲:
成功符合預期!
源碼地址:鏈式調用then源碼
Promises/A+ 規範 要求 onFulfilled、onRejected 在執行上下文堆棧以前不得調用。
當遇到一個異步事件後,並不會一直等待異步事件返回結果,而是會將這個事件掛在與執行棧不一樣的隊列中,咱們稱之爲事件隊列。
當全部同步任務執行完成後,系統纔會讀取"事件隊列"。
事件隊列中的事件分爲宏任務和微任務:
事件隊列就是先執行微任務,再執行宏任務,而宏任務和微任務包含如下事件:
宏任務 | 微任務 |
---|---|
setTimeout | Promise |
setInterval | queueMicrotask |
script(總體代碼塊) | - |
看看下面這個例子,你知道答案嗎?
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
console.log(4);
複製代碼
打印結果的順序是2->4->3->1。事件隊列以下:
new Promise(function(resolve,reject){
console.log(2)
})
複製代碼
console.log(4)
複製代碼
promise.then(function(val){
console.log(val);
})
複製代碼
setTimeout(function () {
console.log(1);
});
複製代碼
所以,想要實現 onFulfilled、onRejected 在執行上下文堆棧以前不得調用,咱們須要把 onFulfilled、onRejected 改形成微任務,這裏使用 queueMicrotask 來模擬實現微任務,代碼以下:
class MyPromise {
then (onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
const realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 建立一個微任務等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 獲取成功回調函數的執行結果
const x = realOnFulfilled(this.value);
// 傳入 resolvePromise 集中處理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 建立一個微任務等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 調用失敗回調,而且把緣由返回
const x = realOnRejected(this.reason);
// 傳入 resolvePromise 集中處理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
if (this.status === STATUS.PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
} else if (this.status === STATUS.FULFILLED) {
fulfilledMicrotask()
} else if (this.status === STATUS.REJECTED) {
rejectedMicrotask()
}
})
return promise2
}
}
複製代碼
下面試試能不能成功?
const mypromise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('成功'), 1000)
})
const mypromise2 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('成功2'), 1000)
})
mypromise.then(data => {
console.log(data, '1')
return mypromise2
}).then(data => {
console.log(data, '2')
})
複製代碼
打印結果如圖:
成功按順序打印。
源碼地址:異步事件源碼
下面將用 Promise/A+ 測試工具 promises-aplus-tests 測試咱們手寫的 Promise 是否符合規範。
npm install promises-aplus-tests -D
複製代碼
MyPromise {
......
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = MyPromise;
複製代碼
"scripts": {
"test:promise": "promises-aplus-tests ./src/手寫系列/Promise/testPromise"
},
複製代碼
npm run test:promise
複製代碼
哇哦,所有成功!!
源碼地址:testPromise.js 源碼
以上,咱們實現了一個符合 Promises/A+ 規範 的 Promise,咱們能夠繼續本身動手,參考 ES6 的 Promise 方法對 MyPromise 進行拓展練習。
總結一下 Promise 其實就是一個幫助咱們執行異步任務的對象,由於 Javascript 單線程的特性,致使必須經過爲異步任務添加回調來獲得異步任務的結果。爲了解決回調地獄,Promise 應運而生。
Promise 經過對異步任務執行狀態的處理,讓咱們能夠在 Promise.then 中獲取任務結果,讓代碼更加清晰優雅。
Promise.then 的鏈式調用,以順序的方式來表達異步流,讓咱們更好的維護異步代碼。
可經過 github源碼 進行實操練習。
但願能對你有所幫助,感謝閱讀~別忘了點個贊鼓勵一下我哦,筆芯❤️