ECMAScript6發佈到如今差很少有5年時間了。在這5年時間裏ES6摧枯拉朽般的將現代前端「改朝換代」,Promise是其中「大將」般的存在,影響着無數的前端庫和API。能夠這麼說,Promise已是現代前端的「血液」。html
儘管通過5年的日日夜夜,儘管書寫過數不盡的Promise。面對着這個時而讓咱們感到真棒,用的舒服、時而坑得咱們踉踉蹌蹌的API,咱們真的瞭解它嗎?前端
相信許多開發者最開始對Promise感到陌生的情景就是:不知道怎麼跟循環結合使用。 例如:git
// 我想將數組下的每一個元素都執行一個函數
fetchSomeData().then((res) => {
res.data.forEach((item) => {
doSomethingFunction(item);
})
}).then(res => {
// 作其餘事
})
複製代碼
這個例子有什麼問題呢?github
問題在於:第一個then回調函數返回的是undefined,就是說第二個then函數並無等doSomethingFunction(item);執行完。事實上,它並不須要等待任何事情,而且能夠在doSomethingFunction(item);執行了幾個後執行。數組
這是一個很是隱蔽的錯誤,由於若是res.data足夠小或者doSomethingFunction()執行的足夠快,可能就不會發現任何問題。promise
如何解決?須要用到Promise.all()。緩存
fetchSomeData().then(res) => {
return Promise.all(res.data.map(item) => {
return doSomethingFunction(item);
})
}).then(res => {
// 作其餘事
})
複製代碼
Promise.all接收一個Promise對象組成的數組做爲參數,當這個數組全部的Promise對象狀態都變成resolved或者rejected的時候,它纔會去調用then方法。bash
fetchSomeData().then((res) => {
doSomethingFunction(res);
}).then(res => {
// 作其餘事
})
複製代碼
這個例子的問題在於第二個then函數獲取的是undefined。使用了side effect去改變而不是返回。異步
每個Promise都有一個then方法,咱們能在then方法中作三件事情:ide
fetchSomeData().then((res) => {
return getId(res);
}).then(res => {
// 我能獲得id
})
複製代碼
使用return 返回第二個Promise,在第二個then方法中就能獲得id。若是沒有return,那麼getId()只是一個side effect,那麼第二個then方法只能獲得undefined。
好比說要對id作一個緩存處理,以下降運行時間。
fetchSomeData().then((res) => {
if (idCache[id]) {
return idCache[id];
}
return getId(res);
}).then(res => {
// 我能獲得id
})
複製代碼
無論id是緩存中的,仍是異步去獲取的,都能返回正確的。
throw error能讓Promise變得更嚴謹。若是要在用戶登出的時候作錯誤處理:
fetchSomeData().then((res) => {
if (logout) {
throw new Error('用戶已登出');
}
if (idCache[id]) {
return idCache[id];
}
return getId(res);
}).then(res => {
// 我能獲得id
}).catch(err=> {
// 作錯誤處理
})
複製代碼
catch方法能獲取獲得錯誤。
若是常常寫出下面內容:
new Promise((resolve, reject) => {
resolve(doSomething())
}).then(...)
複製代碼
其實就是對Promise不熟悉,能夠用更簡短的語句去表達
Promise.resolve(doSomething()).then(...)
複製代碼
一樣Promise.reject()能夠返回當即被拒絕的Promise
Promise.reject(new Error('some error'))
複製代碼
其實catch方法是then(null, function(err) {})的語法糖
下面這兩段代碼是相等的
promise().catch(err => {
// 處理錯誤
})
promise().then(null, err => {
// 處理錯誤
})
複製代碼
但並不意味着下面這兩段代碼是相等的
promise().then((res) => {
return otherPromise(res);
}).cathc(err => {
// 能捕得到到錯誤
})
promise().then(res => {
return otherPromise(res);
}, err => {
// 不能捕得到到錯誤
})
複製代碼
因此,當使用then(resolveHandler, rejectHandler)時,若是它自己發生錯誤,rejectHandler是不會捕得到到的。
出於這個緣由,捕獲錯誤儘可能使用catch方法。
若是要執行一系列的promise,相似Promise.all()方法,但不會並行執行。可能會寫出下面的代碼
function execute(promises) {
var result = Promise.resolve();
promise.forEach(promise => {
result = result.then(promise);
});
return result;
}
複製代碼
不幸的是,這沒法按照預期去執行,仍然是並行執行的。
發生這種狀況的緣由是:預期是不但願對一系列的promise進行操做。可是根據promise規範,一旦建立了promise,它就會開始執行。
所以要用到promise工廠函數
function execute(promiseFactories) {
var result = Promise.reslove();
promiseFactories.forEach(promiseFactory => {
result = result.then(promiseFactory);
});
return result;
}
複製代碼
promise工廠函數很是簡單,只是一個返回promise的函數
function promiseFactory() {
return promiseCreated();
}
複製代碼
這種方法之因此會有效,是由於promise工廠函數直到被調用時才建立promise。它與then函數的工做方式相同
你認爲下面代碼的輸出是什麼?
Promise.resolve('foo').then(Promise.resolve('bar')).then((res) => {
console.log(res);
})
複製代碼
若是你認爲輸出bar,那就錯了。實際上輸出的是foo!
由於當傳遞給then()方法並不是是一個函數時,它實際上執行then(null),這樣先前的promise結果就沒法傳給第二個then方法。
Promise.resolve('foo').then(null).then(res => {
console.log(res) // foo
})
複製代碼
簡而言之,能夠將promise直接傳給then方法,但它並不會按照你的預期去執行。因此你要這樣作
Promise.resolve('foo').then(() => {
return Promise.resolve('bar')
}).then(res => {
console.log(res); // bar
})
複製代碼
所以,請提醒本身:始終要將函數傳遞給then方法
有人說:一回生二回熟。
經歷了上述這六回,相信對promise就像親人通常的熟悉。
上述文章是翻譯、加工自We have a problem with promises
更多文章請移步樓主github,若是喜歡請點一下star,對做者也是一種鼓勵。