Promise的誕生:
js屬於單執行緒,非同步的語言,大部分的非同步操作都是依靠回撥函式進行處理,如果遇到傳送請求依賴上個請求的結果進行下次請求,就會出現巢狀的問題,如下:
var fs = require('fs')
// 每個請求的地址依賴上一個請求
fs.readFile('./name.txt', function (error, data) {
let agePath = data
if (data) {
fs.readFile(agePath, function (error, data) {
let sexPath = data
if (data) {
fs.readFile(sexPath, function (error, data) {
console.log(sexPath)
})
}
})
}
})複製程式碼
大量的巢狀導致程式碼的可讀性變差,維護困難,由此誕生了promise
下面我們來使用一下Promise
let p = new Promise((resolve, reject) => {
setTimeout(function () {
resolve('我是p1')
}, 2000)
})
console.log(promise);
p.then(data => {
console.log(data)
}, err => {
console.log(err)
})複製程式碼
首先是new Promise傳入一個構造器,返回一個Promise物件,構造器內有一個resolve和reject方法,分別用來觸發promise的成功失敗,then內有兩個引數,第一個是註冊成功的回撥,第二個是註冊失敗的回撥函式
簡單實現
function Promise(executor) {
//為什麼要用self
//因為promise捕獲非同步的結果,resolve和reject用來觸發成功失敗的時候this指向已經改變
let self = this
self.status = 'pending'
self.value = undefined
self.reason = undefined
// 成功回撥陣列
self.onFulfilled = []
// 失敗回撥陣列
self.onRejected = []
// 一旦promise狀態變為成功或者失敗就不會在改變
function resolve(value) {
if (self.status !== 'pending') return
self.status = 'resolved'
self.value = value
self.onFulfilled.forEach(fn => fn())
}
function reject(reason) {
if (self.status !== 'pending') return
self.status = 'rejected'
self.reason = reason
self.onRejected.forEach(fn => fn())
}
// 捕獲錯誤,如果Promise例項化出現錯誤直接執行reject
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// then函式用來註冊成功失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this
// 還在等待結果的時候需要註冊到回撥陣列內
if (self.status === 'pending') {
self.onFulfilled.push(() => {
onFulfilled(self.value)
})
self.onRejected.push(() => {
onRejected(self.reason)
})
}
// 如果呼叫.then的時候狀態已經是resolved了
// 我們直接呼叫方法,因為之前的陣列可能執行過了
// 情況出現在回撥完成後,再次呼叫then的方法
if (self.status === 'resolved') {
onFulfilled(self.value)
}
// 同理上面
if (self.status === 'rejected') {
onRejected(self.reason)
}
}複製程式碼
以上程式碼實現簡單的Promise
Promise還支援鏈式呼叫,我們來看看鏈式呼叫的實現方法,需要修改then方法
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
// 防止迴圈引用死迴圈
// 例如
// let p = new Promise((resolve, reject) => {
// resolve(123)
// })
// p.then(data => {
// return p
// })
return reject(new TypeError('迴圈引用'))
}
// 建立變數用來標記,如果進入成功就不會觸發失敗,防止引用其他人的Promise庫同時觸發成功和失敗
let called
// 如果返回值是函式或者是物件,就有可能是其他人return的Promise物件,我們需要等他成功後將結果返回到下一個Promise中
// 如果返回值是其他型別,直接丟擲到下個then的引數中
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
// 捕獲異常,如果then函式存在錯誤,直接丟擲
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
// 遞迴處理,防止返回值還是Promise,直到處理完型別不是函式或者物件丟擲
resolvePromise(promise2, y, resolve, reject)
}, (e) => {
if (called) return
called = true
reject(e)
})
} else {
// 不是函式結果
if (called) return
called = true
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
if (called) return
called = true
resolve(x)
}
}
// then函式用來註冊成功失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this
// 建立一個promise2作為返回值
let promise2
promise2 = new Promise((resolve, reject) => {
// 還在等待結果的時候需要註冊到回撥陣列內
if (self.status === 'pending') {
self.onFulfilled.push(() => {
let x = onFulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
})
self.onRejected.push(() => {
let x = onRejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
})
}
// 如果呼叫.then的時候狀態已經是resolved了
// 我們直接呼叫方法,因為之前的陣列可能執行過了
// 情況出現在回撥完成後,再次呼叫then的方法
if (self.status === 'resolved') {
let x = onFulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
}
// 同理上面
if (self.status === 'rejected') {
let x = onRejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
}
})
return promise2
}
new Promise((resolve, reject) => {
resolve(123)
}).then(data => {
console.log(data) //輸出123
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(456)
}, 1000)
})
}).then(data => {
console.log(data) //輸出456
})複製程式碼
resolvePromise是一個處理函式,當上一個Promise成功後會呼叫下一個Promise的resolve, reject來觸發下一個Promise的回撥
現在我們來完善一下程式碼
// then函式用來註冊成功失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
// 如果沒有傳參,預設返回上一個結果到下個Promise中,在下個Promise的then中呼叫
if (typeof onFulfilled !== 'function') onFulfilled = data => data
// 錯誤必須throw錯誤不然返回出來就是上一個Promise的成功了,throw會被try,catch捕獲,執行reject
if (typeof onRejected !== 'function') onRejected = err => {
throw err
}
let self = this
// 建立一個promise2作為返回值
let promise2
promise2 = new Promise((resolve, reject) => {
// 還在等待結果的時候需要註冊到回撥陣列內
if (self.status === 'pending') {
self.onFulfilled.push(() => {
try {
let x = onFulfilled(self.value)
// 處理函式,當上一個Promise成功後會呼叫下一個Promise的resolve, reject來觸發下一個Promise的回撥
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
self.onRejected.push(() => {
try {
let x = onRejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// 如果呼叫.then的時候狀態已經是resolved了
// 我們直接呼叫方法,因為之前的陣列可能執行過了
// 情況出現在回撥完成後,再次呼叫then的方法
if (self.status === 'resolved') {
try {
let x = onFulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
// 同理上面
if (self.status === 'rejected') {
try {
let x = onRejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
})
return promise2
}
// 直接執行一個只有失敗回撥的函式
// 如果前面沒有寫失敗回撥,會throw到後面有失敗回撥或者是catch內
Promise.prototype.catch = function (onRejected) {
this.then(null, onRejected)
}複製程式碼
之前說的處理都是按順序處理非同步,如果同時執行多個非同步函式,Promise有一個Promise.all方法,專門用來處理非同步,傳入一個Promise陣列,返回一組Promise的結果,如果失敗一個就進入失敗回撥
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 500)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 500)
})
Promise.all([p1, p2]).then(list => {
console.log(list)
}, err => {
console.log(err)
})複製程式碼
我們接下來看看實現Promise.all的實現
Promise.all = function (promises) {
let result = []
let index = 0
return new Promise((resolve, reject) => {
function processData(i, data) {
result[i] = data
index++
if (index === promises.length) {
resolve(result)
}
}
promises.forEach((promise, i) => {
promise.then(data => {
processData(i, data)
}, reject)
})
})
}複製程式碼
按順序執行,resolve後把結果賦值給result對應的索引,然後當index和Promise長度一樣講陣列丟出到返回Promise中,返回順序和輸入保持一致
Promise的實現原理大致就是這樣了,雖然寫法是這樣的,但是本質還是通過callback實現,但是大大提升了程式碼的可讀性。