做爲ES6處理異步操做的新規範,Promise一經出現就廣受歡迎。面試中也是如此,固然此時對前端的要求就不只僅侷限會用這個階段了。下面就一塊兒看下Promise相關的內容。javascript
在開始以前,仍是簡單回顧下Promise是什麼以及怎麼用,直接上來談實現有點空中花園的感受。(下面示例參考自阮大佬es6 Promis,)前端
Promise 是異步編程的一種解決方案,能夠認爲是一個對象,能夠從中獲取異步操做的信息。以替代傳統的回調事件。java
es6規範中,Promise是個構造函數,因此建立以下:git
const promise = new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'resolve');
// 能夠爲同步,以下操做
return resolve('resolve')
})
複製代碼
注意resolve或者reject 一旦執行,後續的代碼能夠執行但就不會再更新狀態(不然這狀態回調就沒法控制了)。 舉個例子:es6
var a = new Promise((resolve,reject)=>{
resolve(1)
console.log('執行代碼,改變狀態')
throw new Error('ss')
})
a.then((res)=>{
console.log('resolved >>>',res)
},(err)=>{
console.log('rejected>>>',err)
})
// 輸出
// 執行代碼,改變狀態
// resolved >>> 1
複製代碼
所以,狀態更新函數以後的再次改變狀態的操做都是無效的,例如異常之類的也不會被catch。 邏輯代碼推薦在狀態更新以前執行。github
構造函數接收一個函數,該函數會同步執行,即咱們的邏輯處理函數,什麼時候執行對應的回調,這部分邏輯仍是要本身管理的。面試
至於如何執行回調,就和入參有關係了。
兩個入參resolve和reject,分別更新不一樣狀態,以觸發對應處理函數。 觸發操做由Promise內部實現,咱們只關注觸發時機便可編程
那麼要實現一個Promise,其構造函數應該是這麼個樣子:json
// 三種狀態
const STATUS = {
PENDING: 'pending',
RESOLVED:'resolved',
REJECTED:'rejected'
}
class Promise{
constructor(fn){
// 初始化狀態
this.status = STATUS.PENDING
// resolve事件隊列
this.resolves = []
// reject事件隊列
this.rejects = []
// resolve和reject是內部提供的,用以改變狀態。
const resovle = (val)=>{
// 顯然這裏應該是改變狀態觸發回調
this.triggerResolve(val)
}
const reject = (val)=>{
// 顯然這裏應該是改變狀態觸發回調
this.triggerReject(val)
}
// 執行fn
try{
fn(resolve,reject)
}catch(err){
// 運行異常要觸發reject,就須要在這裏catch了
this.triggerReject(err)
}
}
then(){
}
}
複製代碼
觸發回調的triggerReject/triggerResolve 作的事情主要兩個:promise
// 觸發 reject回調
triggerReject(val){
// 保存當前值,以供後面調用
this.value = val
// promise狀態一經變化就再也不更新,因此對於非pending狀態,再也不操做
if (this.status === STATUS.PENDING) {
// 更新狀態
this.status = STATUS.REJECTED
// 循環執行回調隊列中事件
this.rejects.forEach((it) => {
it(val)
})
}
}
// resolve 功能相似
// 觸發 resolve回調
triggerResolve(val) {
this.value = val
if(this.status === STATUS.PENDING){
this.status = STATUS.RESOLVED
this.resolves.forEach((it,i)=>{
it(val)
})
}
}
複製代碼
此時執行的話仍是不能達到目的的,由於this.resolves/ this.rejects的回調隊列裏面仍是空呢。 下面就看如何會用then往回調隊列中增長監聽事件。
該方法爲Promise實例上的方法,做用是爲Promise實例增長狀態改變時的回調函數。 接受兩個參數,resolve和reject即咱們所謂成功和失敗回調,其中reject可選
then方法返回的是一個新的實例(也就是新建了一個Promise實例),可實現鏈式調用。
new Promise((resolve, reject) => {
return resolve(1)
}).then(function(res) {
// ...
}).then(function(res) {
// ...
});
複製代碼
前面的結果爲後邊then的參數,這樣能夠實現次序調用。 若前面返回一個promise,則後面的then會依舊遵循promise的狀態變化機制進行調用。
看起來也簡單,then是往事件隊列中push事件。那麼很容易得出下面的代碼:
// 兩個入參函數
then(onResolved,onRejected){
const resolvehandle=(val)=>{
return onResolved(val)
},rejecthandle =(val)=>{
return onRejected(val)
}
// rejecthandle
this.resolves.push(resolvehandle)
this.rejects.push(rejecthandle)
}
複製代碼
此時執行示例代碼,能夠獲得結果了。
new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'done');
}).then((res)=>{
console.log(res)
}) // done
複製代碼
不過這裏太簡陋了,並且then還有個特色是支持鏈式調用其實返回的也是promise 對象。 咱們來改進一下。
then(onResolved,onRejected){
// 返回promise 保證鏈式調用,注意這裏每次then都新建了promise
return new Promise((resolve,reject)=>{
const resolvehandle = (val)=>{
// 對於值,回調方法存在就直接執行,不然不變傳遞下去。
let res = onResolved ? onResolved(val) : val
if(Promise.isPromise(res)){
// 若是onResolved 是promise,那麼就增長then
return res.then((val)=>{
resolve(val)
})
}else {
// 更新狀態,執行完了,後面的隨便
return resolve(val)
}
},
rejecthandle = (val)=>{
var res = onRejected ? onRejected(val) : val;
if (Promise.isPromise(res)) {
res.then(function (val) {
reject(val);
})
} else {
reject(val);
}
}
// 正常加入隊列
this.resolves.push(resolvehandle)
this.rejects.push(rejecthandle)
})
}
複製代碼
此時鏈式調用和promise 的回調也已經支持了,能夠用以下代碼測試。
new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'done');
}).then((res)=>{
return new Promise((resolve)=>{
console.log(res)
setTimeout(resolve, 200, 'done2');
})
}).then((res)=>{
console.log('second then>>', res)
})
複製代碼
不過此時對於同步的執行,仍是有些問題。 由於then中的實現,只是將回調事件假如回調隊列。
對於同步的狀態,then執行在構造函數以後, 此時事件隊列爲空,而狀態已經爲resolved,
因此這種狀態下須要加個判斷,若是非pending狀態直接執行回調。
then(onResolved,onRejected){
/**省略**/
// 剛執行then 狀態就更新,那麼直接執行回調
if(this.status === STATUS.RESOLVED){
return resolvehandle(this.value)
}
if (this.status === STATUS.REJECTED){
return rejecthandle(this.value)
}
})
}
複製代碼
這樣就能解決同步執行的問題。
new Promise((resolve, reject) => {
resolve('done')
}).then((res)=>{
console.log(res)
})
// done
複製代碼
catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。 直接看例子比較簡單:
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
console.log('發生錯誤!', error);
});
複製代碼
此時catch是是getJSON和第一個then運行時的異常,若是隻是在then中指定reject函數,那麼then中執行的異常沒法捕獲。 由於then返回了一個新的promise,同級的reject回調,不會被觸發。 舉個例子:
var a = new Promise((resolve,reject)=>{
resolve(1)
})
a.then((res)=>{
console.log(res)
throw new Error('then')
},(err)=>{
console.log('catch err>>>',err) // 不能catch
})
複製代碼
該catch只能捕獲構造函數中的異常,對於then中的error就不能捕獲了。
var a = new Promise((resolve,reject)=>{
resolve(1)
})
a.then((res)=>{
console.log(res)
throw new Error('then')
}).catch((err)=>{
console.log('catch err>>>',err) // catch err>>> Error: then at <anonymous>:6:11
})
複製代碼
推薦每一個then以後都跟catch來捕獲全部異常。
基於catch方法是.then(null, rejection)或.then(undefined, rejection)的別名這句話,其實實現就比較簡單了。 其內部實現調用then就能夠了。
catch(onRejected){
return this.then(null, onRejected)
}
複製代碼
該方法爲獲取一個指定狀態的Promise對象的快捷操做。 直接看例子比較清晰:
Promise.resolve(1);
// 等價於
new Promise((resolve) => resolve(1));
Promise.reject(1);
// 等價於
new Promise((resolve,reject) => reject(1));
複製代碼
既然是Promise的自身屬性,那麼能夠用es6的static來實現: Promise.reject與其相似,就再也不實現了。
// 轉爲promise resolve 狀態
static resolve(obj){
if (Promise.isPromise(obj)) {
return obj;
}
// 非promise 轉爲promise
return new Promise(function (resolve, reject) {
resolve(obj);
})
}
複製代碼
阮一峯es6入門
promisesaplus.com/
liubin.org/promises-bo…
本想把常見的promise面試題一塊兒加上的,後面就寫成了promise的實現,手動Promise均可以實現的話,相關面試題應該問題不大。這裏附一個JavaScript | Promises interiew 你們能夠看看。完整代碼請戳