接觸過JS的開發人員應該都有用過Promise處理異步編程,它在語法上非常直觀的用“then”去對當下所要做得執行和在期之後所要執行的代碼做了封裝。
function test() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('after one second');
}, 1000);
})
return promise;
}
test().then(res => {
console.log(res);
})
console.log("before one second");
// "before one second"
// "after one second"
對沒有接觸過異步編程的工程師來講,這看起來似乎有魔術般的效應,彷彿是將時間做了停頓。不過花點心思去想Promise的內部機制,大家就會發現,它的核心在於巧妙地應用高階函數的理念將異步操作進行了封裝。如果用最輕量的方法趨勢線上圖的Promise功能,其實幾行代碼便可以解決。
function Promice(fn) {
var self = this
self.status = 'pending' // Promise當前的狀態
self.data = undefined // Promise的值
self.onResolvedCallback = undefined //回調函數
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
if(self.onResolvedCallback) {
self.onResolvedCallback(value)
}
}
}
try {
fn(resolve)
} catch (e) {
reject(e)
}
}
在這裏我將reject抓錯機制去除,針對去看Promise是如何"控制"異步操作的。上圖需要注意的是兩個重點:1. status狀態去跟蹤異步操作是否完成。2. resolve函數要做的就是更新狀態並且調用用戶指定的fn回調函數。有人可能會問:“如果只是單純的更新狀態,如何保證異步操作已經完成呢?”。這裏要注意的是,從Promise得操作說明上,resolve永遠只會在異步回調函數裏出現,所以當resolve啓動時,必然表示異步操作已完成。
有人會注意到onResolvedCallback賦值並沒有在代碼裏實現。這表示在resolve被調用之前,還會有其他的操作執行,也就是"then"函數。
Promice.prototype.then = function (onResolved) {
var self = this
var promise2
if (self.status === 'resolved') {
return promise2 = new Promice(function (resolve, reject) {
var x = onResolved(self.data)
if (x instanceof Promice) {
x.then(resolve, reject)
}
resolve(x)
})
}
if (self.status === 'pending') {
return promise2 = new Promice(function (resolve, reject) {
self.onResolvedCallback = function (value) {
var x = onResolved(self.data)
if (x instanceof Promice) {
x.then(resolve, reject)
}
}
})
}
}
then函數其實在一開始就被調用了,它的責任是去查看status。如果status表明一步操作已經完成,但就直接調用fn。如果還在待定(pending),則將函數賦值給onResolveCallback。當resolve觸發時必會做處理。
總結:Promise會給很多人帶來一種時間被控制的假象,但底層核心邏輯並不複雜。這裏需要注意的是then函數很容易被視爲一個異步操作結束後調用的函數,但這明顯是個錯誤的想法。then通常在異步操作之前已經調用,他的任務往往是將傳入的回調函數放到resolve可以獲取到的作用域。雖然Promise產生的假象能讓我們更直觀的去開發,但也需要了解的是它的底層實現。