其他章節請看:
Promise
Promise 是一種非同步程式設計的選擇
初步認識Promise
用 Promise 來實現這樣一個功能:傳送一個 ajax,返回後輸出 json 資料。請看示例:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
let json = {success: true, data:{}}
resolve(json);
}, 3000);
});
const resolveFn = value => {
console.log(value)
};
const rejectFn = () => {}
promise1.then(resolveFn, rejectFn)
// { success: true, data: {} }
三秒後輸出 json 資料。
Promise 中文翻譯是承諾。首先用 Promise 建構函式建立一個承諾,承諾非同步操作在未來的某時刻完成,接著給承諾(promise1)繫結”已完成“狀態的回撥 resolveFn,以及”已拒絕“狀態的回撥 rejectFn。3秒後返回 json 資料,將承諾的狀態改為”已完成“(resolve(json)
),對應的回撥函式(resolveFn)被呼叫執行。
每個 Promise 都會經歷一個短暫的生命週期,首先是進行中(pending)的狀態,一旦非同步操作執行結束,Promise則變成已處理的狀態。在前面示例中,執行 new Promise() 建立一個 Promise,是進行中的狀態,操作結束後,Promise 可能會進入到以下兩個狀態中的其中一個:
- 已完成(Fulfilled) Promise非同步操作成功完成
- 呼叫 resolve() 進入此狀態
- 已拒絕(Rejected) Promise非同步操作未能成功完成
- 呼叫 reject() 進入此狀態
建立 Promise
通過 new Promise(executor)
可以建立一個新的 Promise。新的 Promise 在沒有 resolve 之前,這個 Promise 的狀態是進行中(或未解決)。
executor(執行器)是一個雙參函式,引數為 resolve 和 reject。Promise 構造器將會在返回新物件之前執行 executor,並傳入 resolve 和 reject 函式。請看示例:
const promise1 = new Promise((resolve, reject) => {
console.log(11)
})
console.log(22)
// 11 22
接著看這個示例:
const p1 = new Promise((resolve, reject) => {
resolve()
console.log(11)
})
p1.then(() => {
console.log('then')
})
console.log(22)
// 11 22 then
呼叫 resolve() 後會觸發一個非同步操作,所以先執行同步(輸出 11 22),最後輸出 then。類似這段程式碼:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('then')
})
console.log(11)
})
console.log(22)
// 11 22 then
then() 方法的兩個引數都是可選的,可以按照任意組合方式監聽 Promise。請看示例:
const promise1 = new Promise((resolve, reject) => {
resolve(1) // {1}
})
promise1.then(() => {
console.log('完成')
})
promise1.then(() => {
console.log('完成')
}, () => {
console.log('拒絕')
})
promise1.then(null, () => {
console.log('拒絕')
})
promise1.catch(() => {
console.log('catch')
})
// 完成 完成
上面前 3 次 then() 呼叫操作的是同一個 Promise。第一個只監聽了完成,錯誤是不報告;第二個同時監聽了完成和拒絕;第三個只監聽了拒絕,成功時不報告;
如果改為reject(1)
({1}),則輸出“拒絕 拒絕 catch”。
無論何時都可以新增新的已完成或已拒絕處理程式。請看示例:
const promise1 = new Promise((resolve, reject) => {
resolve();
});
promise1.then(function(cnt){
console.log(1)
promise1.then(function(cnt){ // {1}
console.log(2)
})
console.log(3)
return 4
}).then(v => {
console.log(v)
})
// 1 3 2 4
這段程式碼在完成處理程式中,向同一個 Promise 新增了另一個完成處理程式(行{1})。
Tip: then() 方法指定的回撥函式將在當前指令碼所有同步任務執行完才會處理。
建立已處理的 Promise
建立未處理的 Promise 最好方法是使用 Promise 建構函式,但如果想用 Promise 表示一個已知值,可以用Promise.resolve() 或者 Promise.reject()。
Promise.resolve
Promise.resolve(value),只接受一個引數並返回一個 Promise。
let p1 = Promise.resolve(11) // {1}
p1.then(v => {
console.log(v)
})
console.log(22)
// 輸出:22 11
行{1}等價於let p1 = new Promise(resolve => resolve(11))
。
如果給 Promise.resolve() 方法傳入一個 Promise,那麼這個 Promise 會被直接返回。請看示例
let p1 = new Promise((resolve, reject) => {
resolve(1) // {1}
})
// resolve另一個promise
let p2 = Promise.resolve(p1)
console.log(p1 == p2)
p2.then(v => {
console.log('resolve')
},v => {
console.log('reject')
})
// true resolve
如果將行{1}改為reject(1)
,輸出“true reject”。
利用此特性,可以將不是 Promise 的值轉為 Promise。
Promise.resolve() 方法允許呼叫時不帶引數,直接返回一個resolved 狀態的 Promise 物件。就像這樣:
let p1 = Promise.resolve()
p1.then(v => {
console.log(v)
})
console.log(22)
// 輸出:22 undefined
非 Promise 的 Thenable 物件
Promise.resolve() 可以接受非 Promise 的 Thenable 物件作為引數,返回的 Promise 將採用 Thenable 物件的最終狀態。請看示例:
// Thenable 物件指:擁有 then() 方法並且接受 resolve 和 reject 兩個引數的普通物件
let thenable = {
then: function(resolve, reject){
reject(1)
}
}
let p1 = Promise.resolve(thenable)
p1.then(v => {
console.log('resolve')
}).catch(v => {
console.log('reject')
})
// reject
這段程式碼,雖然呼叫的是 Promise.resolve(),但 thenable 的狀態是已拒絕(reject(1)
),所以最後輸出 reject。
Promise.reject
Promise.reject() 方法返回一個帶有拒絕原因的 Promise 物件。請看示例:
let p1 = Promise.reject(11)
p1.catch(v => {
console.log(v)
})
console.log(22)
// 輸出:22 11
Promise.reject() 用法比 Promise.resolve() 簡單很多。
比如給 Promise.reject() 方法傳入一個 Promise,效果與 Promise.resolve() 不相同。請看示例:
let p1 = new Promise((resolve, reject) => {
reject(1)
})
let p2 = Promise.reject(p1)
console.log(p1 == p2) // false
再比如給 Promise.reject() 方法傳入一個 thenable,效果與 Promise.resolve() 也不相同。請看示例:
let thenable = {
then: function(resolve, reject){
resolve(1)
}
}
let p1 = Promise.reject(thenable)
p1.then(v => {
console.log('resolve')
}).catch(v => {
console.log('reject')
})
// reject
執行器錯誤
如果執行器內部丟擲錯誤,則 Promise 的拒絕處理程式就會被呼叫,例如:
let p1 = new Promise(function(resolve, reject){
throw new Error('fail')
})
p1.catch(v => {
console.log(v.message) // fail
})
這段程式碼,執行器故意丟擲一個錯誤,每個執行器中都隱含一個 try-catch 塊,所以錯誤會被捕獲並傳入給已拒絕回撥。此例等價於:
let p1 = new Promise(function(resolve, reject){
try{
throw new Error('fail')
}catch(e){
reject(e)
}
})
...
串聯 Promise
將 Promise 串聯起來能實現更復雜的非同步特徵:
let p1 = new Promise((resolve, reject) => {
resolve('10')
})
p1.then(v => {
console.log(v)
}).then(() => {
console.log('finished')
})
每次呼叫 then() 方法或 catch() 方法時,實際上會建立並返回另一個 Promise,只有當第一個 Promise 完成或拒絕後,第二個才會被解決,依此類推。
將這個示例拆開,看起來像這樣:
let p1 = new Promise((resolve, reject) => {
resolve('10')
})
let p2 = p1.then(v => {
console.log(v)
})
p2.then(() => {
console.log('finished')
})
捕獲錯誤
在完成或拒絕處理程式中可能發生錯誤,而 Promise 鏈可以捕獲這些錯誤。請看示例:
let p1 = new Promise((resolve, reject) => {
resolve('10')
})
p1.then(() => {
throw new Error('fail')
}).catch((e) => {
console.log(e.message)
})
// 輸出:fail
這段程式碼在完成處理程式中丟擲一個錯誤。如果在拒絕處理程式中丟擲錯誤,也可以通過相同的方式接收:
let p1 = new Promise((resolve, reject) => {
reject('10')
})
p1.catch(() => {
throw new Error('fail')
}).catch((e) => {
console.log(e.message)
})
// 輸出:fail
儘量在 Promise 鏈的末尾留一個拒絕處理程式,以保證能正確處理所有可能發生的錯誤。請看示例:
如果沒有拒絕處理程式,程式碼可能會這樣:
let p1 = new Promise((resolve, reject) => {
resolve('10')
})
p1.then(() => {
console.log(1) // {1}
}).then(() => {
console.log(2) // {2}
}).then(() => {
console.log(3) // {3}
})
其中三個完成處理程式都有可能出錯,我們可以在末尾新增一個已拒絕處理的程式對這個鏈式統一處理,就像這樣:
let p1 = new Promise((resolve, reject) => {
resolve('10')
})
p1.then(() => {
throw new Error('fail')
console.log(1)
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).catch(e => {
console.log(e.message)
})
// 輸出:fail
這段程式碼是第一個完成處理程式報錯,由於只有末尾才有已拒絕的處理,所以只輸出 fail。
傳遞資料
Promise 鏈的另一個重要特性是可以給下游的 Promise 傳遞資料。請看示例:
let p1 = new Promise((resolve, reject) => {
resolve(1)
})
p1.then(v => {
console.log(v)
return v + 1
}).then(v => {
console.log(v)
})
// 輸出:1 2
在拒絕處理程式中也可以做相同的事:
let p1 = new Promise((resolve, reject) => {
reject(1)
})
p1.catch(v => {
console.log(v)
return v + 1
}).then(v => {
console.log(v)
})
// 輸出:1 2
拒絕處理中返回值仍然可以在下一個Promise的完成處理程式中使用,必要時,即使其中一個Promise失敗,也能恢復整條鏈的執行。
在 Promise 鏈中返回 Promise
前面我們通過返回值給下游 Promise 傳遞資料,如果返回值是 Promise 物件,則會通過一個額外的步驟來確定下一步該怎麼走。請看示例:
let p1 = new Promise((resolve, reject) => {
reject(1)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10) // {1}
}, 3000)
})
p1.catch(v => {
console.log('等待3秒')
return p2
}).then(v => {
console.log(`resolve: ${v}`)
}, v => {
console.log(`reject: ${v}`)
})
/*
等待3秒
// 等待3秒後輸出
resolve: 10
*/
這段程式碼,在 Promise 鏈中返回一個 Promise(p2),由於 p2 的狀態是已完成({1}),所以下一步則進入已完成處理程式。
響應多個Promise
es6 提供了 Promise.all() 和 Promise.race() 兩個方法來監聽多個 Promise。
Promise.all()
Promise.all 只接收一個引數並返回一個Promise,該引數是含有多個受監視Promise的可迭代物件(例如陣列),只有當所有 Promise 都被解決,返回的 Promise 才會被解決。請看示例:
let p1 = new Promise((resolve, reject) => {
resolve(1)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 3000)
})
let p3 = Promise.all([p1, p2])
p3.then(value => {
console.log(Array.isArray(value)) // {1}
console.log(value)
}).catch(v => {
console.log(Array.isArray(v))
console.log(v)
})
// true
// [1, 2]
這段程式碼,Promise.all 監聽了兩個 Promise,其中一個需要過3秒才被置為已解決,當兩個 Promise 都被解決,才會輸出結果。其中 value({1})是陣列。
如果被 Promise.all 監聽的其中一個被拒絕,那麼不用等所有 Promise 都完成就會立即被拒絕。在上面示例的基礎上,將 resolve(1)
改為 reject(1)
,立即輸出false 1
,無需等待另一個 Promise 解決。拒絕處理程式總是接受一個值而非陣列。
Promise.race()
Promise.race() 與 Promise.all() 類似,不同之處是隻要有一個被解決,返回的 Promise 就被解決。請看示例:
let p1 = new Promise((resolve, reject) => {
resolve(1)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 3000)
})
let p3 = Promise.race([p1, p2])
console.log(p3 === p1)
p3.then(v => {
console.log(Array.isArray(v))
console.log(`resolve, ${v}`)
}).catch(v => {
console.log(Array.isArray(v))
console.log(`reject, ${v}`)
})
/*
false
false
resolve, 1
*/
無需等待 p2 被解決,立刻輸出。實際上,傳給 Promise.race() 方法的 Promise 會進行競選,以決定哪一個先被解決,如果先解決的是已完成 Promise,則返回已完成的 Promise,如果先解決的是已拒絕的 Promise,則返回已拒絕的Promise。請看示例:
let p1 = new Promise((resolve, reject) => {
reject(1)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2 resolve')
resolve(2)
}, 3000)
})
let p3 = Promise.race([p1, p2])
p3.then(v => {
console.log(Array.isArray(v))
console.log(`resolve, ${v}`)
}).catch(v => {
console.log(Array.isArray(v))
console.log(`reject, ${v}`)
})
/*
false
reject, 1
p2 resolve
*/
p2 雖然被忽略,但仍會執行。
其他章節請看: