Promise 使用技巧九則

blank

Promise 使用技巧九則

本文譯自9 Promising Promise Tips 。首發https://qianduan.group

工程師們,你們總說Pormise好用!但有時候用起來是不是還很懵逼。本文傳授給你九條實用的Promise 使用技巧,幫助你和它建立起良好的關係!

1.你可以在.then回調裡返回Promise

我必須大聲喊出來:

是的!你可以.then回調裡返回Promise!

而且,返回的promise會在接下來的.then被自動打開(unwrapped):

. then ( r => { // 这是一个{ statusCode: 200 } promise return serverStatusPromise ( r ); }) . then ( resp => { // 200;注意上面的promise 被自动unwrap 了console . log ( resp . statusCode ); })

2.每次調用.then都會產生一個新的Promise

如果你熟悉JavaScript的鍊式調用,對這種用法一定不陌生。

調用.then.catch時都會創建一個新的Promise。這個新的Promise可以繼續使用.then或者.catch

var statusProm = fetchServerStatus(); var promA = statusProm.then(r => (r.statusCode === 200 ? "good" : "bad")); var promB = promA.then(r => (r === "good" ? "ALL OK" : "NOTOK")); var promC = statusProm.then(r => fetchThisAnotherThing());

上面出現的promise 可以用下面這個流程圖來表示:

重點注意, promApromBpromC雖然相關,但都是不同的promise實例。

我喜歡把這種.then鏈當做一個巨大的水暖系統,如果父節點出了故障,熱水將無法流入到自節點中。例如,如果promB故障了,其他節點不受影響,但是如果statusProm出問題了,其他節點就會被影響,也就是被rejected

3.在任何情況下,Promise resolve/reject狀態都是一致的

這是Promise 之所以好用的原因。簡單理解,就是如果一個promise在多個地方使用,當它被resolve或者reject的時候,都會獲得通知。

而且promise是無法被修改的,因此它可以隨意傳遞。

function yourFunc() { const yourAwesomeProm = makeMeProm(); // 无论坏叔叔如何消费你的promise,你的promise 都可以正常工作yourEvilUncle(yourAwesomeProm); return yourAwesomeProm.then(r => importantProcessing(r)); } function yourEvilUncle(prom) { // 坏叔叔return prom.then(r => Promise.reject("destroy!!")); }

Promise 的設計避免了惡意的破壞,如我所說:“沒事,可以把promise 隨便扔!”

4. Promise 構造函數不是萬金油

我發現有些工程師在任何地方都會使用Promise 的constructor,還認為這就是promise 的使用方式。這是不對的,根本原因就是constructor API 與原來callback API 很像,老的習慣很難改。

如果你的代碼中遍布Promise constructor,你的做法就是錯的!

如果你想更進一步,擺脫回調函數,你應該盡量減少Promise 構造函數的使用。

Promise 構造函數正確的使用場景如下:

return new Promise((res, rej) => { fs.readFile("/etc/passwd", function(err, data) { if (err) return rej(err); return res(data); }); });

Promise constructor只在將回調轉成promise時使用。

看一個冗餘的例子:

// 错误用法return new Promise((res, rej) => { var fetchPromise = fetchSomeData(.....); fetchPromise .then(data => { res(data); // 错误的方式}) .catch(err => rej(err)) }) // 正确用法// 看上去对就是对的return fetchSomeData(...);

在Node.js中,推薦使用util.promisify 。用來將回調API 轉成promise 式的:

const {promisify} = require('util'); const fs = require('fs'); const readFileAsync = promisify(fs.readFile); readFileAsync('myfile.txt', 'utf-8') .then(r => console.log(r)) .catch(e => console.error(e));

5. 使用Promise.resolve

JavaScript提供了Promise.resolve API,是產生Promise對象的一種快捷方式,這個promise對像是被resolve的。

var similarProm = new Promise(res => res(5)); // 相当于var prom = Promise.resolve(5);

這有很多使用場景,我最喜歡的一個是,將一個同步的對象轉成一個promise:

// 将一个同步函数转成异步的function foo() { return Promise.resolve(5); }

也可以用來在不確定返回值是普通對像還是promise 時,將返回值封裝為promise 對象:

function goodProm(maybePromise) { return Promise.resolve(maybePromise); } goodProm(5).then(console.log); // 5 // 这个promise resolve 成5 var sixPromise = fetchMeNumber(6); goodProm(sixPromise).then(console.log); // 6 // 5,注意,每层promise 都被自动unwrap 了goodProm(Promise.resolve(Promise.resolve(5))).then(console.log);

6. 使用Promise.reject

Promise.resolve類似,它也是一種快捷寫法

var rejProm = new Promise((res, reject) => reject(5)); rejProm.catch(e => console.log(e)) // 5

我最喜歡的Promise.reject 的用法是,儘早地reject:

function foo(myVal) { if (!mVal) { return Promise.reject(new Error('myVal is required')) } return new Promise((res, rej) => { // 这些将巨大的callback 转成promise }) }

.then中使用reject:

.then(val => { if (val != 5) { return Promise.reject('Not Good'); } }) .catch(e => console.log(e)) // Not Good

7. 使用Promise.all

JavaScript還提供了Promise.all ,但它不是什麼快捷方式。

可以如下總結它的算法:

接受一个 promise 的数组
    等待所有这些 promise 完成
    返回一个新的 Promise,将所有的 resolve 结果放进一个数组里
    只要有一个  promise 失败/rejected,这个新的 promise 将会被 rejected

下例展示了所有promise 都resolve 的情況:

var prom1 = Promise.resolve(5); var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200} Proimise.all([prom1, prom2]) .then([val1, val2] => { // notice that it resolves into an Array console.log(val1); // 5 console.log(val2.statusCode); // 200 })

下例展示有一個失敗的情況:

var prom1 = Promise.reject(5); var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200} Proimise.all([prom1, prom2]) .then([val1, val2] => { console.log(val1); console.log(val2.statusCode); }) .catch(e => console.log(e)) // 5, jumps directly to .catch

注意Promise.all是一點不笨,只要有一個promise被reject了,它就直接reject,不會等到其他promise完成。

8.不要害怕reject或者不要在每一個.then後面使用.catch

我們是不是常常感到有很多隱藏的錯誤沒有被處理?

不用擔心像下面這樣寫:

return fetchSomeData(...);

你可以在任何你想處理的地方解決或者延續rejection。

處理掉rejection

這很簡單,在.catch回調中,無論你返回什麼都會變成resolve,除非你返回一個Promise.reject ,才會延續rejection。

.then(() => 5.length) // <-- something wrong happenned here .catch(e => { return 5; // <-- making javascript great again }) .then(r => { console.log(r); // 5 }) .catch(e => { console.error(e); // this function will never be called :) })

reject rejection

reject rejection 的方法就是什麼都不做。通常,父函數比起當前函數更擅長處理rejection。

要記住一個要點,一旦你寫了.catch就意味著rejection已經被處理了,這與同步的try/catch類似。

如果你確實想要阻斷rejection(我強烈不推薦這麼做):

.then(() => 5.length) // <-- something wrong happenned here .catch(e => { errorLogger(e); // do something impure return Promise.reject(e); // reject it, Yes you can do that! }) .then(r => { console.log(r); // this .then (or any subsequent .then) will never be called as we rejected it above :) }) .catch(e => { console.error(e); //<-- it becomes this catch's problem })

.then(x,y)then(x).catch(x)

.then接受第二個回調參數來處理錯誤。雖然與then(x).catch(x)看一起類似,但卻有所不同,不同點在於可捕獲的錯誤。

下面例子很好地說了這個問題:

.then(function() { return Promise.reject(new Error('something wrong happened')); }).catch(function(e) { console.error(e); // something wrong happened }); .then(function() { return Promise.reject(new Error('something wrong happened')); }, function(e) { // callback handles error coming from the chain above the current `.then` console.error(e); // no error logged });

9.避免.then嵌套

這個原則理解起來很簡單,就是避免在.then裡面繼續使用.then或者.catch 。相信我,這絕對是可以避免的。

// 错误用法request(opts) .catch(err => { if (err.statusCode === 400) { return request(opts) .then(r => r.text()) .catch(err2 => console.error(err2)) } }) // 正确用法request(opts) .catch(err => { if (err.statusCode === 400) { return request(opts); } return Promise.reject(err); }) .then(r => r.text()) .catch(err => console.erro(err));

就算是如下這種情況,也可以使用Promise.all來解決:

.then(myVal => { const promA = foo(myVal); const promB = anotherPromMake(myVal); return promA .then(valA => { return promB.then(valB => hungryFunc(valA, valB)); // very hungry! }) })

可以像下面這樣:

.then(myVal => { const promA = foo(myVal); const promB = anotherPromMake(myVal); return Promise.all([prom, anotherProm]) }) .then(([valA, valB]) => { // putting ES6 destructing to good use console.log(valA, valB) // all the resolved values return hungryFunc(valA, valB) })

好了,真心希望本文可以幫到你更好地理解Promise!

What do you think?

Written by marketer

blank

[多行文本] 樣式怎麼沒了?

blank

對Cycle.js 的一些思考