熟悉而陌生API:Promise
阿新 • • 發佈:2020-10-12
前言
ES6 釋出到現在差不多有5年時間了。在這5年時間裡ES6摧枯拉朽般的將現代前端“改朝換代”,Promise是其中“大將”般的存在,影響著無數的前端庫和API。可以這麼說,Promise已經是現代前端的“血液”。 儘管經過5年的日日夜夜,儘管書寫過數不盡的Promise。面對著這個時而讓我們感到真棒,用的舒服、時而坑得我們踉踉蹌蹌的API,我們真的瞭解它嗎?陌生情景一:怎麼和迴圈結合
相信許多開發者最開始對Promise感到陌生的情景就是:不知道怎麼跟迴圈結合使用。 例如:// 我想將陣列下的每個元素都執行一個函式 fetchSomeData().then((res) => { res.data.forEach((item) => { doSomethingFunction(item); }) }).then(res => { // 做其他事 })
這個例子有什麼問題呢? 問題在於:第一個then回撥函式返回的是undefined,就是說第二個then函式並沒有等doSomethingFunction(item);執行完。事實上,它並不需要等待任何事情,並且可以在doSomethingFunction(item);執行了幾個後執行。 這是一個非常隱蔽的錯誤,因為如果res.data足夠小或者doSomethingFunction()執行的足夠快,可能就不會發現任何問題。 如何解決?需要用到Promise.all()。
Promise.all()
fetchSomeData().then(res) => { return Promise.all(res.data.map(item) => { return doSomethingFunction(item); }) }).then(res => { // 做其他事 })
Promise.all接收一個Promise物件組成的陣列作為引數,當這個陣列所有的Promise物件狀態都變成resolved或者rejected的時候,它才會去呼叫then方法。
陌生情景二:沒有return
fetchSomeData().then((res) => { doSomethingFunction(res); }).then(res => { // 做其他事 })
這個例子的問題在於第二個then函式獲取的是undefined。使用了side effect去改變而不是返回。 每一個Promise都有一個then方法,我們能在then方法中做三件事情:
- return 另一個Promise
- return 一個值
- throw 一個錯誤
返回一個Promise
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
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方法能獲取得到錯誤。
陌生情景三:不知道Promise.resolve()與Promise.reject()
如果經常寫出下面內容:new Promise((resolve, reject) => { resolve(doSomething()) }).then(...)
其實就是對Promise不熟悉,可以用更簡短的語句去表達
Promise.resolve
Promise.resolve(doSomething()).then(...)
同樣Promise.reject()可以返回立即被拒絕的Promise
Promise.reject
Promise.reject(new Error('some error'))
陌生情景四:then().catch()與then(resolveHandler, rejectHandler)傻傻分不清楚
其實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,類似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函式的工作方式相同
陌生情景六: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作者:Nolan Lawson 連結: 熟悉而陌生API:Promise 來源:github