1. 程式人生 > 實用技巧 >熟悉而陌生API:Promise

熟悉而陌生API:Promise

前言

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