隨著 async/await 功能普及,現在寫 JavaScript 時,比較沒機會用上 Promise.then() 。但最近寫 Cypress 測試時發現, Cypress command 也用上了類似 Promise 的介面。
他們採用了 Bluebird 這個 Promise 實作,又在外面包了一個稱為 Chainer 的結構。那個 Chainer 有著自己的 then() 。
這時候就無法使用專門為 Promise 設計的 async/await 。
於是我想分享一些以前使用 ES6 Promise 的小技巧。
Promise 最有名的就是 chaining 了, chaining 讓你可以避開 callback hell !
以在 Node.js 中讀檔為例:
fs.access(filepath1, fs.constants.F_OK, (err) => { if (err) return; fs.readFile(filepath1, (err, file) => { if (err) return; console.log(file); }); });
如果我們用上 Promise 介面的 fs-extra ,上面的程式可以改寫成:
const fs = require('fs-extra'); fs.pathExists(filepath) .then(exists => fsex.readFile(filepath)) .then(file => console.log(file));
有趣的是,按 Promise/A+ 規格 2.2.7 小節, then 的參數(也就是 onFulfilled 和 onRejected 兩個函數)最後 return 的值,不管是不是 Promise ,都要再經過一次 Promise 解析程序( Promise Resolution Procedure )。
2.2.7.1 If either
Promises/A+onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
.
於是我們可以自由傳回同步或非同步的結果,在下一個 onFulfilled 函數中,都能直接取用:
const example = fetch('http://example.com').then(res => res.text()); const ans = Promise.resolve(42); ans .then(x => { console.log(x); // 42 return Promise.resolve(x); }) .then(x => { console.log(x); // 還是 42 ,沒有被 Promise 包起來 return example; }) .then(x => { console.log(x); // example.com 的內容 });
也可以利用這點,往後傳遞額外資訊,但要混用同步與非同步資料,就會稍微麻煩一些:
getUser() .then(user => getPosts(user.id).then(posts => ({ user, posts })) ) .then(({ user, posts }) => { console.log(user, posts); });
Promise 只會 resolve 一次,而且內容確定下來後不會改變,所以我們也可以在遞迴中使用它們:
function add(pa, pb) { return pa.then(a => pb.then(b => a + b)); } function fib(i) { if (i === 0 || i === 1) return Promise.resolve(1); return add(fib(i - 1), fib(i - 2)); } fib(5).then(x => console.log(x)); // 8
但因為 Promise/A+ 2.2.4 節的設計限制,讓 onFulfilled 和 onRejected 不會馬上執行,所以這樣的技巧無法用在迴圈中。
2.2.4
Promises/A+onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code.
Promise.then() 這種介面越來越常見,例如 Java 的 CompletableFuture 有著 thenApply() 、 C# 的 Task 有 ContinueWith() 。在這些環境中,也能用上同樣的技巧。
希望這些經驗可以幫助大家在沒有 async/await 的時候,簡化您的程式。