在 ECMAScript 5 以 Callback 實現 Asynchronous,如 Node 所提供的 Function 就是 Callback-Based,我們可實作一個 Higher Order Function 專門將 Callback-Based Function 轉成 ECMAScript 2015 的 Promise-Based Function。
Version
macOS Mojave 10.14.6
VS Code 1.38.1
Node 12.10.0
ECMAScript 2015
Wink-fp 0.0.20
Callback-based Function
let fs = require('fs');
fs.readFile(__filename, 'utf8', (err, data) => {
if (err) console.log(err);
else console.log(data);
});
fs.readFile()
為 Node 代表性 function,負責讀取文字檔內容:
- 第一個 argument 為
檔案名稱
- 第二個 argument 為 encoding
- 第三個 argument 為 callback,負責讀取文字內容與 error handling
Node 的 callback 習慣第一個 argument 為 error
,第二個 argument 為 data
,使用對 err
做 if else
判斷處理 data
。
Promise-based Function
let fs = require('fs');
let readFile = (path, encoding) => new Promise((resolve, reject) => {
fs.readFile(path, encoding, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
readFile(__filename, 'utf8')
.then(x => console.log(x))
.catch(e => console.error(e));
我們可自行實作 readFile()
回傳 promise,則取 data
在 then()
的 callback,處理 err
在 catch()
的 callback,這種 promise-based function 明顯比 callback-based function 可讀性高。
第 3 行
let readFile = (path, encoding) => new Promise((resolve, reject) => {
fs.readFile(path, encoding, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
其實readFile()
底層仍然是呼叫 fs.readFile()
,只是使用了 promise constructor 做包裝,若 if (err)
成立則呼叫 reject()
產生 rejected promise,否則呼叫 resolve()
產生 fulfilled promise。
Higher Order Function
let fs = require('fs');
let promisify = fn => (...args) => new Promise((resolve, reject) =>
fn(...args, (err, data) => (err) ? reject(err): resolve(data)));
let readFile = promisify(fs.readFile);
readFile(__filename, 'utf8')
.then(x => console.log(x))
.catch(e => console.error(e));
觀察 readFile()
,其實使用 promise constructor 有一定的套路,因此可自行實作 promisify()
higher order function,專門將 callback-based function 轉成 promise-based function。
第 3 行
let promisify = fn => (...args) => new Promise((resolve, reject) =>
fn(...args, (err, data) => (err) ? reject(err): resolve(data)));
由於 promisify()
接受任意 argument 的 function,因此使用了 ES6 的 ...arg
rest argument。
Wink-fp
let fs = require('fs');
let util = require('util');
let readFile = util.promisify(fs.readFile);
readFile(__filename, 'utf8')
.then(x => console.log(x))
.catch(e => console.error(e));
Node 已經提供了 util.promisify()
,可直接使用。
前端則可使用 Wink-fp 的 promisify()
將 callback-based function 轉成 promise-based function。
promisify()
(a...,(a... -> b)) -> (a... -> Promise)
將 callback-based function 轉成 promised-based function
(a...,(a... -> b))
:callback-based function(a... -> Promise)
:回傳為 promise-based function
Conclusion
- ES6 提出 promise 後,理論上都可用 promise 取代 callback
- 透過 Node 的
util.promisify()
或 Wink-fp 的promisify()
,可將 callback-based function 轉成 promise-based function - Node 的 callback-based function 有其一定的 signature,但前端 function 多半 signature 不固定,但抽 higher order function 的套路是一樣的
Reference
Marius Schulz, Convert a Callback-Based JavaScript Function to a Promise-Based One