熟悉而陌生API:Promise

前言

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()。緩存

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

陌生情景二:沒有return

fetchSomeData().then((res) => {
    doSomethingFunction(res);
}).then(res => {
    // 作其餘事
})
複製代碼

這個例子的問題在於第二個then函數獲取的是undefined。使用了side effect去改變而不是返回。異步

每個Promise都有一個then方法,咱們能在then方法中作三件事情:ide

  • 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

結尾

更多文章請移步樓主github,若是喜歡請點一下star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索