開發中Promise
是及其經常使用的語法,基本上對於異步的處理大都是經過Promise
來進行完成。Promise規範有不少,ES6最終採用的是Promise/A+ 規範
,因此如下代碼也基本是基於這個規範來進行編寫的。javascript
首先咱們先列舉Promise的全部實例方法跟靜態方法html
實例方法前端
new Promise((resolve, reject) => {...}).then(() => {console.log('rsolve成功回調')}, () => {console.log('reject失敗回調')})
new Promise((resolve, reject) => {...}).catch(() => {console.log('reject失敗方法')})
new Promise((resolve, reject) => {...}).finally(() => {console.log('成功失敗都進入')})
Promise
靜態方法java
Promise.resolve(value)
返回Promise
實例Promise.reject(value)
返回Promise
實例Promise.all(promises)
: 傳入數組格式的Promise
並返回新的Promise
實例,成功便按照順序把值返回出來,其中一個失敗則直接變成失敗Promise.race(promises)
: 傳入數組格式的Promise
並返回新的Promise
實例,成功與失敗取決第一個的完成方式Promise
狀態一旦肯定變不可再發生變化,有如下三個狀態:pending
、fulfilled
、rejected
Promise
在瀏覽器中的實現是放於微任務隊列中的,須要作微任務的處理(JavaScript中的Event Loop(事件循環)機制
)node
class Promise {
_value
_state = 'pending'
_queue = []
constructor(fn) {
if (typeof fn !== 'function') {
throw 'Promise resolver undefined is not a function'
}
/* new Promise((resolve, reject) => { resolve: 成功 reject: 失敗 }) */
fn(this._resolve.bind(this), this._reject.bind(this))
}
// 接收1-2參數,第一個爲成功的回調,第二個爲失敗的回調
then(onFulfilled, onRejected) {
// 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊
if (this._state === 'fulfilled') {
onFulfilled?.(this._value)
return
}
// reject同理
if (this._state === 'rejected') {
onRejected?.(this._value)
return
}
// Promise尚未完成,push到一個隊列,到時候完成的時候,執行這個隊列裏面對應的函數
this._queue.push({
onFulfilled,
onRejected,
})
}
// 接收失敗的回調
catch(onRejected) {
// 至關於直接調用then傳入失敗的回調
this.then(null, onRejected)
}
// 成功與失敗都執行的回調
finally(onDone) {
const fn = () => onDone()
this.then(fn, fn)
}
// 成功resolve
_resolve(value) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
this._state = 'fulfilled'
// 把值存起來,當再次調用的時候直接取這個值就行,由於Promise一旦肯定就不會發生改變了
this._value = value
// 執行前面.then方法裏面push函數形式的參數,這樣就執行對應的方法了。
this._queue.forEach((callback) => {
callback.onFulfilled?.(this._value)
})
}
// 失敗reject
_reject(error) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
this._state = 'rejected'
this._value = error
this._queue.forEach((callback) => {
callback.onRejected?.(this._value)
})
}
}
複製代碼
調用邏輯:git
經過then
方法傳入函數形式的參數,也就是onFulfilled
=> then((onFulfilled, onRejected) => {...})
github
在then
方法中把onFulfilled
函數放入_queue
這個集合中。 => this._queue.push({ onFulfilled, onRejected })
segmentfault
等異步回調完成,執行resolve
函數,這個時候就調用_queue
收集好的經過then
方法註冊的函數。統一執行這些函數,這樣就達到異步回調完成,執行對應的then
方法裏面的函數數組
// 結果打印
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
p.then((res) => {
console.log(res) // => success
})
// reject
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('fail')
}, 1000)
})
p1.catch((res) => {
console.log(res) // => fail
})
// finally
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
p2.finally(() => {
console.log('done') // => done
})
複製代碼
在線代碼演示promise
在瀏覽器中 Promise
完成以後會被推入微任務,因此咱們也須要進行這塊的處理。瀏覽器中使用MutationObserver,node可使用process.nextTick
class Promise {
...
// 推入微任務
_nextTick(fn) {
if (typeof MutationObserver !== 'undefined') { // 瀏覽器經過MutationObserver實現微任務的效果
// 這塊能夠單獨拿出來共用,避免沒必要要的開銷,否則每次都須要生成節點。
const observer = new MutationObserver(fn)
let count = 1
const textNode = document.createTextNode(String(count))
observer.observe(textNode, {
characterData: true
})
textNode.data = String(++count)
} else if (typeof process.nextTick !== 'undefined') { // node端經過process.nextTick來實現
process.nextTick(fn)
} else {
setTimeout(fn, 0)
}
}
// 成功resolve
_resolve(value) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
// 推入微任務
this._nextTick(() => {
this._state = 'fulfilled'
this._value = value
this._queue.forEach((callback) => {
callback.onFulfilled?.(this._value)
})
})
}
// 失敗reject
_reject(error) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
// 推入微任務
this._nextTick(() => {
this._state = 'rejected'
this._value = error
this._queue.forEach((callback) => {
callback.onRejected?.(this._value)
})
})
}
...
}
複製代碼
一般Promise
會處理多個異步請求,有時候請求之間是有相互依賴關係的。
例如:
const getUser = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123'
})
}, 500)
})
}
const getDataByUser = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// ....
resolve({a: 1})
}, 500)
})
}
// 使用
getUser().then((user) => {
return getDataByUser(user.userId)
}).then((res) => {
console.log(res)// {a: 1}
})
複製代碼
getDataByUser
依賴getUser
請求回來的用戶信息,這裏就須要用到Promise
鏈式的調用,下面咱們來改動咱們的代碼
class Promise {
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this))
}
...
// 1. 這時候then方法須要返回新的Promise了,由於須要進行鏈式調用,而且下一個then方法接受上一個then方法的值
// 2. 返回的Promise確定是一個新的Promise,否則就會共用狀態跟返回結果了。
// 3. 把上一個then方法中的返回值當作下一個Promise resolve的值
then(onFulfilled, onRejected) {
// 返回新的Promise
return new Promise((resolve, reject) => {
// 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊,這個時候能夠直接把值返給函數就行了
if (this._state === 'fulfilled' && onFulfilled) {
this._nextTick(onFulfilled.bind(this, this._value))
return
}
if (this._state === 'rejected' && onRejected) {
this._nextTick(onRejected.bind(this, this._value))
return
}
/* 把當前Promise的then方法的參數跟新的Promise的resolve, reject存到一塊兒,以此來作關聯。 這樣就能把上一個Promise中的onFulfilled與新的Promise中的resolve兩個關聯到一塊,而後即可以作賦值之類的操做了。reject同理 */
this._queue.push({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
// reject同理
_resolve(value) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
// 上面示例裏面其實返回的是一個Promise,而不是直接返回的值,因此,這裏咱們須要作一個特殊處理。
// 就是resolve()的值若是是Promise的對象,咱們須要解析Promise的結果,而後在把值傳給resolve
if (typeof value === 'object' && typeof value.then === 'function') {
// 咱們能夠把當前_resolve方法傳遞下去,由於then方法中的參數,一經下個Promise resolve,便會執行then方法對應的參數,而後把對應的值傳入。
// 這樣就能取到Promise中的值
// this._resove => obj.onFulfilled?.(this._value)
// this._reject => obj.onRejected?.(this._value)
value.then(this._resolve.bind(this), this._reject.bind(this))
return
}
// 推入微任務
this._nextTick(() => {
this._state = 'fulfilled'
this._value = value
this._queue.forEach((obj) => {
// 接受onFulfilled返回值
const val = obj.onFulfilled?.(this._value)
// reoslve這個值,此時 onFulfilled 是當前Promise then方法中的第一個參數: Promise.then((res) => {consolle.log(res)})
// obj.resolve是新的Promise的resolve函數,這樣就把then方法中的返回值傳給下一個Promise
obj.resolve(val)
})
})
}
...
}
複製代碼
調用邏輯:
微任務採用MutationObserver
跟process.nextTick
來進行實現
Promise
鏈式調用,這裏經過把then
方法中的(onFulfilled, onRejected)
參數與新返回的Promise
中的(resolve, reject)
關聯到一塊兒。
一旦上一個Promise
成功,調用onFulfilled
函數,就能夠把onFulfilled
中返回的值,放到新的Promise的resolve中。
若是遇到resolve
的值是Promise
對象,遞歸進行解析,而後再把值返回出去
完整代碼
class Promise {
_value
_state = 'pending'
_queue = []
constructor(fn) {
if (typeof fn !== 'function') {
throw new Error('Promise resolver undefined is not a function')
}
/* new Promise((resolve, reject) => { resolve: 成功 reject: 失敗 }) */
fn(this._resolve.bind(this), this._reject.bind(this))
}
// 接收1-2參數,第一個爲成功的回調,第二個爲失敗的回調
then(onFulfilled, onRejected) {
// 返回新的Promise
return new Promise((resolve, reject) => {
// 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊,這個時候能夠直接把值返給函數就行了
if (this._state === 'fulfilled' && onFulfilled) {
this._nextTick(onFulfilled.bind(this, this._value))
return
}
if (this._state === 'rejected' && onRejected) {
this._nextTick(onRejected.bind(this, this._value))
return
}
// 把當前Promise的then方法的參數跟新的Promise的resolve, reject存到一塊兒,以此來作關聯
this._queue.push({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
// 接收失敗的回調
catch(onRejected) {
return this.then(null, onRejected)
}
// 成功與失敗都執行的回調
finally(onDone) {
return this.then((value) => {
onDone()
return value
}, (value) => {
// console.log(value)
onDone()
throw value
})
}
// 推入微任務
_nextTick(fn) {
if (typeof MutationObserver !== 'undefined') { // 瀏覽器
// 這塊能夠單獨拿出來共用,避免沒必要要的開銷,否則每次都須要生成節點。
const observer = new MutationObserver(fn)
let count = 1
const textNode = document.createTextNode(String(count))
observer.observe(textNode, {
characterData: true
})
textNode.data = String(++count)
} else if (typeof process.nextTick !== 'undefined') { // node
process.nextTick(fn)
} else {
setTimeout(fn, 0)
}
}
// 成功resolve
_resolve(value) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
// 上面示例裏面其實返回的是一個Promise,而不是直接返回的值,因此,這裏咱們須要作一個特殊處理。
// 就是若是resolve()的若是是Promise的對象,咱們須要解析Promise的結果,而後在把值傳給resolve
if (typeof value === 'object' && typeof value.then === 'function') {
// 咱們能夠把當前_resolve方法傳遞下去,由於then方法中的參數,一經下個Promise resolve,便會執行then方法對應的參數,而後把對應的值傳入。
// 這樣就能取到Promise中的值
// this._resove => obj.onFulfilled?.(this._value)
// this._reject => obj.onRejected?.(this._value)
value.then(this._resolve.bind(this), this._reject.bind(this))
return
}
// 推入微任務
this._nextTick(() => {
this._state = 'fulfilled'
this._value = value
this._queue.forEach((obj) => {
// 使用try catch 來捕獲onFulfilled存在函數內部錯誤的狀況
try {
// 接受onFulfilled返回值,若是不存在,把this._value往下傳遞
const val = obj.onFulfilled ? obj.onFulfilled(this._value) : this._value
// reoslve這個值,此時 onFulfilled 是當前Promise then方法中的第一個參數: Promise.then((res) => {consolle.log(res)})
// obj.resolve是新的Promise的resolve函數,這樣就把then方法中的返回值傳給下一個Promise
obj.resolve(val)
} catch (e) {
obj.reject(e)
}
})
})
}
// 失敗reject
_reject(error) {
if (this._state !== 'pending') return
this._nextTick(() => {
this._state = 'rejected'
this._value = error
this._queue.forEach((obj) => {
try {
const val = obj.onRejected ? obj.onRejected(this._value) : this._value
// 當前 reject執行完畢以後,會返回新的Promise,應該是能正常resolve的,因此這裏要用 resolve, 不該該繼續使用reject來讓下個Promise執行失敗流程
obj.resolve(val)
} catch (e) {
obj.reject(e)
}
})
})
}
}
複製代碼
總共有4個靜態方法: Promise.resolve
、Promise.reject
、Promise.all
、Promise.race
,統一返回的都是新的Promise。
class Promise {
...
/** * 直接resolve */
static resolve(value) {
// 是Promise直接返回
if (value instanceof Promise) {
return value
} else if (typeof value === 'object' && typeof value.then === 'function') {
// 傳入的對象含有then方法
const then = value.then
return new Promise((resolve) => {
then.call(value, resolve)
})
} else {
// 正常返回值,直接返回新的Promise在resolve這個值
return new Promise((resolve) => resolve(value))
}
}
/** * 直接reject, 測試下Promise.reject並沒作特殊處理,因此直接返回便可。 */
static reject(value) {
return new Promise((resolve, reject) => reject(value))
}
/** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功便按照順序把值返回出來,其中一個失敗則直接變成失敗 */
static all(promises) {
return new Promise((resolve, reject) => {
let count = 0
let arr = []
// 按照對應的下標push到數組裏面
promises.forEach((promise, index) => {
// 轉換成Promise對象
Promise.resolve(promise).then((res) => {
count++
arr[index] = res
if (count === promises.length) {
resolve(arr)
}
}, err => reject(err))
})
})
}
/** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功與失敗取決第一個的完成方式 */
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
// 轉換成Promise對象
Promise.resolve(promise).then((res) => {
// 誰先執行直接resolve, 或reject
resolve(res)
}, err => reject(err))
})
})
}
...
}
複製代碼
class Promise {
_value
_state = 'pending'
_queue = []
constructor(fn) {
if (typeof fn !== 'function') {
throw new Error('Promise resolver undefined is not a function')
}
/* new Promise((resolve, reject) => { resolve: 成功 reject: 失敗 }) */
fn(this._resolve.bind(this), this._reject.bind(this))
}
/** * 接收1-2參數,第一個爲成功的回調,第二個爲失敗的回調 * * @param {*} onFulfilled * @param {*} onRejected * @return {*} * @memberof Promise */
then(onFulfilled, onRejected) {
// 返回新的Promise
return new Promise((resolve, reject) => {
// 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊,這個時候能夠直接把值返給函數就行了
if (this._state === 'fulfilled' && onFulfilled) {
this._nextTick(onFulfilled.bind(this, this._value))
return
}
if (this._state === 'rejected' && onRejected) {
this._nextTick(onRejected.bind(this, this._value))
return
}
// 把當前Promise的then方法的參數跟新的Promise的resolve, reject存到一塊兒,以此來作關聯
this._queue.push({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
/** * 接收失敗的回調 * * @param {*} onRejected * @return {*} * @memberof Promise */
catch(onRejected) {
return this.then(null, onRejected)
}
/** * 成功與失敗都執行的回調 * * @param {*} onDone * @return {*} * @memberof Promise */
finally(onDone) {
return this.then((value) => {
onDone()
return value
}, (value) => {
onDone()
// 直接報錯,能夠在try catch中捕獲錯誤
throw value
})
}
/** * 直接resolve * * @static * @param {*} value * @return {*} * @memberof Promise */
static resolve(value) {
if (value instanceof Promise) {
return value
} else if (typeof value === 'object' && typeof value.then === 'function') {
// 傳入的對象含有then方法
const then = value.then
return new Promise((resolve) => {
then.call(value, resolve)
})
} else {
return new Promise((resolve) => resolve(value))
}
}
/** * 直接reject, 測試下reject在Promise.reject中沒作特殊處理 * * @static * @param {*} value * @return {*} * @memberof Promise */
static reject(value) {
return new Promise((resolve, reject) => reject(value))
}
/** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功便按照順序把值返回出來,其中一個失敗則直接變成失敗 * * @static * @param {*} promises * @memberof Promise */
static all(promises) {
return new Promise((resolve, reject) => {
let count = 0
let arr = []
if (Array.isArray(promises)) {
if (promises.length === 0) {
return resolve(promises)
}
promises.forEach((promise, index) => {
// 轉換成Promise對象
Promise.resolve(promise).then((res) => {
count++
arr[index] = res
if (count === promises.length) {
resolve(arr)
}
}, err => reject(err))
})
return
} else {
reject(`${promises} is not Array`)
}
})
}
/** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功與失敗取決第一個的完成方式 * * @static * @param {*} promises * @return {*} * @memberof Promise */
static race(promises) {
return new Promise((resolve, reject) => {
if (Array.isArray(promises)) {
promises.forEach((promise, index) => {
// 轉換成Promise對象
Promise.resolve(promise).then((res) => {
resolve(res)
}, err => reject(err))
})
} else {
reject(`${promises} is not Array`)
}
})
}
// 推入微任務
_nextTick(fn) {
if (typeof MutationObserver !== 'undefined') { // 瀏覽器
// 這塊能夠單獨拿出來共用,避免沒必要要的開銷,否則每次都須要生成節點。
const observer = new MutationObserver(fn)
let count = 1
const textNode = document.createTextNode(String(count))
observer.observe(textNode, {
characterData: true
})
textNode.data = String(++count)
} else if (typeof process.nextTick !== 'undefined') { // node
process.nextTick(fn)
} else {
setTimeout(fn, 0)
}
}
// 成功resolve
_resolve(value) {
// 狀態肯定了,就再也不發生變化了
if (this._state !== 'pending') return
// 上面示例裏面其實返回的是一個Promise,而不是直接返回的值,因此,這裏咱們須要作一個特殊處理。
// 就是若是resolve()的若是是Promise的對象,咱們須要解析Promise的結果,而後在把值傳給resolve
if (typeof value === 'object' && typeof value.then === 'function') {
// 咱們能夠把當前_resolve方法傳遞下去,由於then方法中的參數,一經下個Promise resolve,便會執行then方法對應的參數,而後把對應的值傳入。
// 這樣就能取到Promise中的值
// this._resove => obj.onFulfilled?.(this._value)
// this._reject => obj.onRejected?.(this._value)
value.then(this._resolve.bind(this), this._reject.bind(this))
return
}
// 經過打印測試,若是直接在線程裏進行resolve, 狀態跟值好像是直接就改變了,並無執行完主流程,在執行微任務的時候進行修改的。
// 因此把狀態改變和值的修改移出了微任務,只有在走回調的時候才經過微任務進行處理
this._state = 'fulfilled'
this._value = value
// 推入微任務
this._nextTick(() => {
this._queue.forEach((obj) => {
// 使用try catch 來捕獲onFulfilled存在函數內部錯誤的狀況
try {
// 接受onFulfilled返回值,若是不存在,把this._value往下傳遞
const val = obj.onFulfilled ? obj.onFulfilled(this._value) : this._value
// reoslve這個值,此時 onFulfilled 是當前Promise then方法中的第一個參數: Promise.then((res) => {consolle.log(res)})
// obj.resolve是新的Promise的resolve函數,這樣就把then方法中的返回值傳給下一個Promise
obj.resolve(val)
} catch (e) {
obj.reject(e)
}
})
})
}
// 失敗reject
_reject(error) {
if (this._state !== 'pending') return
this._state = 'rejected'
this._value = error
this._nextTick(() => {
this._queue.forEach((obj) => {
try {
// 用戶傳入的函數內部錯誤捕獲
if (obj.onRejected) {
const val = obj.onRejected(this._value)
// 當前 reject執行完畢以後,會返回新的Promise,應該是能正常resolve的,因此這裏要用 resolve, 不該該繼續使用reject來讓下個Promise執行失敗流程
obj.resolve(val)
} else {
// 遞歸傳遞reject錯誤
obj.reject(this._value)
}
} catch (e) {
obj.reject(e)
}
})
})
}
}
複製代碼
本項目完整代碼:GitHub
以上就是Promise的實現方案,固然這個跟完整的Promises/A+規範
是有區別的。這裏只是用作於學習之用。
QQ羣:前端打雜羣
公衆號:冬瓜書屋