Node.js 內(nèi)置的 util
模塊有一個(gè) promisify()
方法,該方法將基于回調(diào)的函數(shù)轉(zhuǎn)換為基于 Promise 的函數(shù)。這使您可以將 Promise 鏈和 async/await
與基于回調(diào)的 API 結(jié)合使用。
例如,Node.js 的 fs
模塊在讀取文件時(shí),需要使用回調(diào):
const fs = require('fs') fs.readFile('./package.json', function callback(err, buf) { const obj = JSON.parse(buf.toString('utf8')) console.log(obj.name) // 'Example' -> package.json 包名 })
我們可以使用 util.promisify()
將 fs.readFile()
的回調(diào)函數(shù)轉(zhuǎn)換為返回 Promise 函數(shù):
const fs = require('fs') const util = require('util') // 將 fs.readFile() 轉(zhuǎn)換為一個(gè)接受相同參數(shù)但返回 Promise 的函數(shù)。 const readFile = util.promisify(fs.readFile) // 現(xiàn)在可以將 readFile() 與 await 一起使用! const buf = await readFile('./package.json') const obj = JSON.parse(buf.toString('utf8')) console.log(obj.name) // 'Example'
promisify 是如何工作的?
util.promisify()
在后臺(tái)是如何工作的?npm 上有一個(gè) polyfill,您可以在這里閱讀完整的實(shí)現(xiàn)。您也可以在這里找到 Node.js 的實(shí)現(xiàn),不過為了便于理解,polyfill 更易于閱讀?!就扑]學(xué)習(xí):《nodejs 教程》】
util.promisify()
背后的關(guān)鍵思想是向傳入的參數(shù)添加回調(diào)函數(shù)。該回調(diào)函數(shù)解析或拒絕 promisified 函數(shù)返回的 Promise。
為了便于理解,下面是一個(gè)非常簡(jiǎn)化的 util.promisify()
自定義實(shí)現(xiàn)示例:
const fs = require('fs') // util.promisify() 的簡(jiǎn)化實(shí)現(xiàn)。不包括所有情況,不要在 prod 環(huán)境中使用此選項(xiàng)! function promisify(fn) { return function() { const args = Array.prototype.slice.call(arguments) return new Promise((resolve, reject) => { fn.apply(this, [].concat(args).concat([(err, res) => { if (err != null) { return reject(err) } resolve(res) }])) }) } } // 將 fs.readFile() 轉(zhuǎn)換為一個(gè)接受相同參數(shù)但返回 Promise 的函數(shù)。 const readFile = promisify(fs.readFile) // 現(xiàn)在可以將 readFile() 與 await 一起使用! const buf = await readFile('./package.json') const obj = JSON.parse(buf.toString('utf8')) console.log(obj.name) // 'Example'
那么這是什么意思呢?首先,util.promisify()
向傳入的參數(shù)添加 1 個(gè)額外參數(shù),然后使用這些新參數(shù)調(diào)用原始函數(shù)。這意味著底層函數(shù)需要支持該數(shù)量的參數(shù)。因此,如果您要調(diào)用myFn()
具有 2 個(gè)類型參數(shù)的promisified 函數(shù) [String, Object]
,請(qǐng)確保原始函數(shù)支持[String, Object, Function]
。
那么這意味著什么呢?首先,util.promisify()
向傳入的參數(shù)添加一個(gè)額外參數(shù),然后使用這些新參數(shù)調(diào)用原始函數(shù)。這意味著基礎(chǔ)函數(shù)需要支持該數(shù)量的參數(shù)。因此,如果您使用 [String, Object]
類型的 2 個(gè)參數(shù)調(diào)用 promisified 函數(shù) myFn()
,請(qǐng)確保原始函數(shù)支持 [String, Object, Function]
。
其次,util.promisify()
對(duì)函數(shù)上下文(this
)有影響。
丟失上下文
丟失上下文(this
)意味著函數(shù)調(diào)用以錯(cuò)誤的值結(jié)束。丟失上下文是轉(zhuǎn)換函數(shù)的常見問題:
class MyClass { myCallbackFn(cb) { cb(null, this) } } const obj = new MyClass() const promisified = require('util').promisify(obj.myCallbackFn) const context = await promisified() console.log(context) // 打印 undefined 而不是 MyClass 實(shí)例!
請(qǐng)記住,this
包含函數(shù)被調(diào)用時(shí)的屬性的任何對(duì)象。因此,您可以通過將 promisified 函數(shù)設(shè)置為同一對(duì)象的屬性來保留上下文:
class MyClass { myCallbackFn(cb) { cb(null, this) } } const obj = new MyClass() // 保留上下文,因?yàn)?promisified 是 obj 的屬性 obj.promisified = require('util').promisify(obj.myCallbackFn) const context = await obj.promisified() console.log(context === obj) // true