Promise原理分析
前言
最近討論到了 Promise
,此前知道也使用過它,但是對於其原理卻不甚瞭解。
於是翻了翻 MDN
上的文件,又找了幾篇文章看了看,研究了研究。
最終,自己嘗試了一番,對於其原理也有所瞭解。
Promise的使用
先回顧一下 Promise
的使用。
這裡只是簡單的呼叫,如果需要系統學習,還是移步 MDN
上的文件。
new Promise((resolve) => {
setTimeout(() => {
console.log(1)
resolve(1)
})
})
.then((response) => (console.log(++response), response))
.then((response) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1)
resolve(1)
})
})
})
.then((response) => (console.log(++response), response))
執行結果如下:
程式碼分析
一個
Promise
就是一個代表了非同步操作最終完成或者失敗的物件。---- 摘自 MDN
第一步,例項化一個 Promise
物件。
它的引數就是一個函式,此函式會接受兩個引數,分別是 resolve
和 reject
。
當操作成功時,則執行 resolve
。
當操作失敗時,則執行 reject
。
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve(1)
})
})
回撥之後的操作可以用其提供的 then
方法。
then
接受兩個引數,分別是 resolve
和 reject
。原理同上。
new Promise((resolve) => {
setTimeout(() => {
console.log(1)
resolve(1)
})
})
.then((response) => (console.log(++response), response))
從上述程式碼裡可以看出,我們使用了 setTimeout
。
也就是說,then
方法會在 setTimeout
裡的方法之前執行。
如果沒有 Promise
的特殊機制,輸出的結果可能就是 undefined、undefined、1、1
了。
這就是 Promise
的魅力,也是我想要知道的地方。
原理分析
既然是要分析其原理,那麼按照其特性和使用方式來仿造一個是一個不錯的選擇。(主要是我不知道它的原始碼是什麼<(_ _)>)
基本結構
先實現例項化的部分。
從上面的程式碼,我們已經知道,例項化的時候,只需要傳 一個引數 就可以了,同時這個 引數 是一個 函式。
從執行結果來看,在我們例項化的時候,這個 函式 已經 執行 了。
並且這個函式會接收到 兩個引數,這 兩個引數 都是 函式。
function PromiseDemo(foo) {
if (typeof foo != 'function') {
throw Error('Promise 的引數必須是函式')
}
// 成功後執行的函式
let resolve = response => {}
// 失敗後執行的函式
let reject = response => {}
// 例項化後,立即執行函式
foo(resolve, reject)
}
then
方法部分。
then
方法是 Promise
物件的一個方法,我們需要將其暴露出來。
同時,then
方法支援鏈式的呼叫,所以返回值肯定是一個 Promise
物件。
function PromiseDemo(foo) {
// 成功後執行的函式
let resolve = response => {}
// 失敗後執行的函式
let reject = response => {}
// 實現 then 函式
let then = (onResolved, onRejected) => {
return new PromiseDemo((resolved, rejected) => {
// todo...
})
}
// 例項化後,立即執行函式
foo(resolve, reject)
return {
then: then
}
}
到這裡,基本的結構已經有了,我們已經可以執行相關程式碼了,就是還沒有想要的效果。
屬性完善
通過一開始的示例分析,我們可以看出,then
引數接收到的引數是從上層傳遞到下層的。
這句話有點繞,引數的引數。所以,Promise
物件就要有一個存放返回值的屬性,這裡就給它命名為 data
。
同時,then
函式的引數,是在 setTimeout
執行之後才執行的。那麼,Promise
物件就要有一個存放任務的列表,這裡就給它命名為 queue
。
到這裡,問題就來了,什麼時候應該將任務放入任務列表呢?是還沒有執行的,還是執行成功的,還是執行失敗的?
說到這裡,我們就要有一個標識表明這個任務是處於什麼狀態,是未執行,還是已執行,還是執行失敗。這裡就給它命名為 state
,值分別為 pending
、resolved
、rejected
。
上述文字有點長,主要就是定義了 Promise
的三個屬性:state
、queue
、data
。
let state = 'pending'
, queue = []
, data
實現 then
函式
為什麼不返回 this
上面分析例項的時候有說到,為了能夠達成鏈式呼叫,我們在 then
函式的返回值要是一個 Promise
物件。
而且基礎程式碼裡編寫的是直接例項化了一個新的物件,那麼可能會造成疑惑,直接返回 this
不行麼?
答案是否定的,原因很簡單,是由於 Promise
自身的屬性決定了不能直接返回 this
,不然其中任何一個屬性發生修改都會對後面的操作造成影響。
編碼
迴歸正軌。
let then = (onResolved, onRejected) => {
return new PromiseDemo((resolved, rejected) => {
// todo...
})
}
then
函式有兩個引數,並且返回值是一個例項化的 Promise
物件。
也就是說,在處理的過程中,我們將會涉及四個函式,分別是:onResolved
, onRejected
, resolved
, rejected
。
先分析一下四個函式的執行情況。
如果之前的 Promise
尚未處理(使用的非同步程式碼,比如:setTimeout
, ajax
),那麼當前的狀態 state = 'pending'
,那我們就不能執行任何一個函式。
這個時候我們就要將這四個函式儲存好,表明這屬於同一個任務。
// 如果還沒執行結束,則將任務推向佇列,並返回
if (state == 'pending') {
queue.push([onResolved, onRejected, resolved, rejected])
return
}
如果之前的 Promise
已經執行,那麼當前的狀態 state = 'resolved'
或者 state = 'rejected'
。
這個時候我們就要分辨到底是什麼情況了。
// callback 是 then 函式兩個引數中的一個
// next 是 Promise 成功失敗函式中的一個
let callback, next
// 判斷是否出錯
if (state == 'resolved') {
callback = onResolved
next = resolved
} else {
callback = onRejected
next = rejected
}
當我們處理完之後,我們就要執行相關函式了。
callback(data)
為了能夠將引數傳遞給下游,callback
的返回值需要直接給 next
使用。
next(callback(data))
好了,結束。
真的結束了麼?
由於 callbak
是使用者呼叫 then
函式傳遞過來的,那使用者真的一定會傳引數麼?使用者傳過來的引數一定是一個函式麼?那麼直接執行 callback
就會有問題!!!
// 如果是函式,則直接執行
// 並將引數傳遞給下游
if (typeof callback == 'function') {
next(callback(data))
return
}
next(data)
好了,這樣貌似沒什麼問題了,整理一下程式碼。
function then(onResolved, onRejected) {
return new PromiseDemo((resolved, rejected) => {
handle(onResolved, onRejected, resolved, rejected)
})
}
function handle(onResolved, onRejected, resolved, rejected) {
// 如果還沒執行結束,則將任務推向佇列,並返回
if (state == 'pending') {
queue.push([onResolved, onRejected, resolved, rejected])
return
}
let callback, next
// 判斷是否出錯
if (state == 'resolved') {
callback = onResolved
next = resolved
} else {
callback = onRejected
next = rejected
}
// 如果是函式,則直接執行
// 並將引數傳遞給下游
if (typeof callback == 'function') {
next(callback(data))
return
}
next(data)
}
實現 resolve
函式
相對於 then
函式的複雜,resolve
函式就相對簡單點。
為了避免重複執行,這裡直接將 state
置為 rejected
。
先考慮一下,如果傳過來的值是一個 Promise
物件,那我們該怎麼處理?如果是一個非 Promise
物件的引數,該怎麼處理?
由於 Promise
物件本身是擁有 then
函式的,我們應該執行它,這樣這個物件處理後的值,會流轉到下一層。
let resolve = response => {
state = 'resolved'
if (response && (typeof response == 'function' || typeof response == 'object')) {
if (response.hasOwnProperty('then') && typeof response.then == 'function') {
response.then.call(response, resolve, reject)
return
}
}
data = response
runCallbacks()
}
這裡有個 runCallbacks
是幹嘛的呢?
還記得我們將未執行的任務放到 queue
中了不?那麼當前的任務執行完,是不是該執行下一個任務了呢?
let runCallbacks = () => {
queue.forEach(callback => handle.apply(null, callback))
}
OK,到這裡,resolve
函式已經編寫完成了。
實現其它
Promise
還有其它的一些方法,例如:reject
、catch
等。
這裡就不一一寫了,下面該試驗一下我們的 PromiseDemo
是否有用了。
demo 測試
先來原始碼
function PromiseDemo(foo) {
if (typeof foo != 'function') {
throw Error('Promise 的引數必須是函式')
}
let state = 'pending'
, queue = []
, data
function then(onResolved, onRejected) {
return new PromiseDemo((resolved, rejected) => {
handle(onResolved, onRejected, resolved, rejected)
})
}
function handle(onResolved, onRejected, resolved, rejected) {
// 如果還沒執行結束,則將任務推向佇列,並返回
if (state == 'pending') {
queue.push([onResolved, onRejected, resolved, rejected])
return
}
let callback, next
// 判斷是否出錯
if (state == 'resolved') {
callback = onResolved
next = resolved
} else {
callback = onRejected
next = rejected
}
// 如果是函式,則直接執行
// 並將引數傳遞給下游
if (typeof callback == 'function') {
next(callback(data))
return
}
next(data)
}
let runCallbacks = () => {
queue.forEach(callback => handle.apply(null, callback))
}
let reject = response => {
state = 'rejected'
data = response
runCallbacks()
}
let resolve = response => {
state = 'resolved'
if (response && (typeof response == 'function' || typeof response == 'object')) {
if (response.hasOwnProperty('then') && typeof response.then == 'function') {
response.then.call(response, resolve, reject)
return
}
}
data = response
runCallbacks()
}
try {
foo(resolve, reject)
} catch (e) {
reject(e)
}
return {
then: then
}
}
執行一開始的 demo
,結果如下:
流程圖
說也說了,寫也寫了,整個流程是什麼樣子的呢?
參考資料
最後
終於寫完了,有點囉嗦,基本上把我想表達的都表達出來了。
-- EOF --
本文轉載自IMJCW
原文連結:Promise原理分析
相關文章
- js非同步發展歷史與Promise原理分析JS非同步Promise
- Promise原理解讀Promise
- Promise原理解析Promise
- 淺析Promise原理Promise
- 通俗易懂 Promise 原理Promise
- Promise 原始碼分析Promise原始碼
- Promise原始碼分析Promise原始碼
- Promise的祕密(Promise原理解析以及實現)Promise
- Promise原理講解 && 實現一個Promise物件 (遵循Promise/A+規範)Promise物件
- Promise原理探究及實現Promise
- ES6之promise原理Promise
- promise原理就是這麼簡單Promise
- Promise實現原理(附原始碼)Promise原始碼
- promise原理—一步一步實現一個promisePromise
- 從使用到原理,實現符合Promise A+規範的Promise方法Promise
- 講給小白聽的Promise原理剖析Promise
- ES6 Promise 及實現原理Promise
- 從設計模式角度分析Promise:手撕Promise並不難設計模式Promise
- 來了老弟,最簡單的Promise原理Promise
- Promise實現的基本原理(一)Promise
- 淺談Generator和Promise原理及實現Promise
- 扒一扒PROMISE的原理,大家不要怕!Promise
- Promise實現的基本原理(二)Promise
- 用es5實現es6的promise,徹底搞懂promise的原理Promise
- 探祕Promise原理-帶你原生實現Es6之Promise (從0到1)Promise
- SparseArray原理分析
- SparseIntArray原理分析
- Handler原理分析
- mysqldump原理分析MySql
- HSF原理分析
- ThreadLocal原理分析thread
- ReentrantLock原理分析ReentrantLock
- Xposed原理分析
- AST 原理分析AST
- SpringIOC原理分析Spring
- KVO原理分析
- ThreadLocal 原理分析thread
- promise、async、await非同步原理與執行順序PromiseAI非同步