研究 Promise 的動機大致有如下幾點:git
對其 api 的不熟悉以及對實現機制的好奇;github
不少庫(好比 fetch)是基於 Promise 封裝的,那麼要了解這些庫的前置條件得先熟悉 Promise;ajax
要了解其它更爲高級的異步操做得先熟悉 Promise;segmentfault
基於這些目的,實踐了一個符合 Promise/A+ 規範的 repromiseapi
本札記系列總共三篇文章,做爲以前的文章 Node.js 異步異聞錄 的拆分和矯正。數組
在實現一個符合 Promise/A+ 規範的 promise 以前,先了解下 Promise/A+ 核心,想更全面地瞭解能夠閱讀 Promise/A+規範promise
Promise.resolve() 括號內有 4 種狀況app
/* 跟 Promise 對象 */
Promise.resolve(Promise.resolve(1))
// Promise {state: "resolved", data: 1, callbackQueue: Array(0)}
/* 跟 thenable 對象 */
var thenable = {
then: function(resolve, reject) {
resolve(1)
}
}
Promise.resolve(thenable)
// Promise {state: "resolved", data: 1, callbackQueue: Array(0)}
/* 普通參數 */
Promise.resolve(1)
// Promise {state: "resolved", data: 1, callbackQueue: Array(0)}
/* 不跟參數 */
Promise.resolve()
// Promise {state: "resolved", data: undefined, callbackQueue: Array(0)}
複製代碼
相較於 Promise.resolve(),Promise.reject() 原封不動地返回參數值異步
對於 Promise.all(arr) 來講,在參數數組中全部元素都變爲決定態後,而後才返回新的 promise。函數
// 如下 demo,請求兩個 url,當兩個異步請求返還結果後,再請求第三個 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)
Promise.all([p1, p2])
.then((datas) => { // 此處 datas 爲調用 p1, p2 後的結果的數組
return request(`http://some.url.3?a=${datas[0]}&b=${datas[1]}`)
})
.then((data) => {
console.log(msg)
})
複製代碼
對於 Promise.race(arr) 來講,只要參數數組有一個元素變爲決定態,便返回新的 promise。
// race 譯爲競爭,一樣是請求兩個 url,當且僅當一個請求返還結果後,就請求第三個 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)
Promise.race([p1, p2])
.then((data) => { // 此處 data 取調用 p1, p2 後優先返回的結果
return request(`http://some.url.3?value=${data}`)
})
.then((data) => {
console.log(data)
})
複製代碼
經過下面這個案例,提供回調函數 Promise 化的思路。
function foo(a, b, cb) {
ajax(
`http://some.url?a=${a}&b=${b}`,
cb
)
}
foo(1, 2, function(err, data) {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
複製代碼
如上是一個傳統回調函數使用案例,只要使用 Promise.wrap() 包裹 foo 函數就對其完成了 promise 化,使用以下:
const promiseFoo = Promise.wrap(foo)
promiseFoo(1, 2)
.then((data) => {
console.log(data)
})
.catch((err) => {
console.log(err)
})
複製代碼
Promise.wrap 的實現邏輯也順帶列出來了:
Promise.wrap = function(fn) {
return funtion() {
const args = [].slice.call(arguments)
return new Promise((resolve, reject) => {
fn.apply(null, args.concat((err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}))
})
}
}
複製代碼
這幾個 api 比較簡單,合起來一塊兒帶過
Promise.resolve(1)
.then((data) => {console.log(data)}, (err) => {console.log(err)}) // 鏈式調用,能夠傳一個參數(推薦),也能夠傳兩個參數
.catch((err) => {console.log(err)}) // 捕獲鏈式調用中拋出的錯誤 || 捕獲變爲失敗態的值
.done() // 能捕獲前面鏈式調用的錯誤(包括 catch 中),能夠傳兩個參數也可不傳
複製代碼
事件循環:同步隊列執行完後,在指定時間後再執行異步隊列的內容。
之因此要單列事件循環,由於代碼的執行順序與其息息相關,此處用 setTimeout 來模擬事件循環;
下面代碼片斷中,① 處執行完並不會立刻執行 setTimeout() 中的代碼(③),而是此時有多少次 then 的調用,就會從新進入 ② 處多少次後,再進入 ③
excuteAsyncCallback(callback, value) {
const that = this
setTimeout(function() {
const res = callback(value) // ③
that.excuteCallback('fulfilled', res)
}, 4)
}
then(onResolved, onRejected) {
const promise = new this.constructor()
if (this.state !== 'PENDING') {
const callback = this.state === 'fulfilled' ? onResolved : onRejected
this.excuteAsyncCallback.call(promise, callback, this.data) // ①
} else {
this.callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) // ②
}
return promise
}
複製代碼
this.callbackArr.push() 中的 this 指向的是 ‘上一個’ promise,因此類 CallbackItem 中,this.promise 存儲的是'下一個' promise(then 對象)。
class Promise {
...
then(onResolved, onRejected) {
const promise = new this.constructor()
if (this.state !== 'PENDING') { // 第一次進入 then,狀態是 RESOLVED 或者是 REJECTED
const callback = this.state === 'fulfilled' ? onResolved : onRejected
this.excuteAsyncCallback.call(promise, callback, this.data) // 綁定 this 到 promise
} else { // 從第二次開始之後,進入 then,狀態是 PENDING
this.callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) // 這裏的 this 也是指向‘上一個’ promise
}
return promise
}
...
}
class CallbackItem {
constructor(promise, onResolve, onReject) {
this.promise = promise // 相應地,這裏存儲的 promise 是來自下一個 then 的
this.onResolve = typeof(onResolve) === 'function' ? onResolve : (resolve) => {}
this.onReject = typeof(onRejected) === 'function' ? onRejected : (rejected) => {}
}
...
}
複製代碼
實踐的更多過程能夠參考測試用例。有好的意見歡迎交流。