“Promisification”是一个长单词,表示一个简单的转换。它是将接受回调的函数转换为返回 Promise 的函数。
这种转换在现实生活中经常需要,因为许多函数和库都是基于回调的。但 Promise 更方便,因此将它们 promisify 是有意义的。
为了更好地理解,我们来看一个例子。
例如,我们有来自章节 介绍:回调 的loadScript(src, callback)
。
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
// usage:
// loadScript('path/script.js', (err, script) => {...})
该函数使用给定的src
加载脚本,然后在出错的情况下调用callback(err)
,或者在加载成功的情况下调用callback(null, script)
。这是使用回调的广泛约定,我们之前已经见过。
让我们将它 promisify。
我们将创建一个新函数loadScriptPromise(src)
,它执行相同操作(加载脚本),但返回一个 Promise 而不是使用回调。
换句话说,我们只传递 src
(没有 callback
),并获得一个 promise,如果加载成功,则使用 script
解析,否则使用错误拒绝。
它在这里
let loadScriptPromise = function(src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) reject(err);
else resolve(script);
});
});
};
// usage:
// loadScriptPromise('path/script.js').then(...)
正如我们所看到的,新函数是原始 loadScript
函数的包装器。它调用它,提供自己的回调,该回调转换为 promise resolve/reject
。
现在 loadScriptPromise
非常适合基于 promise 的代码。如果我们更喜欢 promise 而不是回调(我们很快就会看到更多原因),那么我们将使用它。
在实践中,我们可能需要对多个函数进行 promisify,因此使用助手是有意义的。
我们将称之为 promisify(f)
:它接受要 promisify 的函数 f
,并返回一个包装器函数。
function promisify(f) {
return function (...args) { // return a wrapper-function (*)
return new Promise((resolve, reject) => {
function callback(err, result) { // our custom callback for f (**)
if (err) {
reject(err);
} else {
resolve(result);
}
}
args.push(callback); // append our custom callback to the end of f arguments
f.call(this, ...args); // call the original function
});
};
}
// usage:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);
代码看起来可能有点复杂,但它本质上与我们在上面编写的相同,同时对 loadScript
函数进行 promisify。
对 promisify(f)
的调用返回 f
(*)
周围的包装器。该包装器返回一个 promise,并将调用转发到原始 f
,在自定义回调 (**)
中跟踪结果。
在这里,promisify
假设原始函数期望具有恰好两个参数 (err, result)
的回调。这是我们最常遇到的情况。然后,我们的自定义回调完全采用正确的格式,并且 promisify
非常适合这种情况。
但是,如果原始 f
期望具有更多参数的回调 callback(err, res1, res2, ...)
呢?
我们可以改进我们的助手。让我们制作一个更高级版本的 promisify
。
- 当作为
promisify(f)
调用时,它应该与上述版本类似。 - 当作为
promisify(f, true)
调用时,它应该返回解析为回调结果数组的 promise。这完全适用于具有许多参数的回调。
// promisify(f, true) to get array of results
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // our custom callback for f
if (err) {
reject(err);
} else {
// resolve with all callback results if manyArgs is specified
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
}
// usage:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);
正如您所看到的,它本质上与上面相同,但是 resolve
仅使用一个或所有参数调用,具体取决于 manyArgs
是否为真。
对于更奇特的回调格式,例如根本没有 err
的格式:callback(result)
,我们可以手动对这些函数进行 promisify,而无需使用助手。
还有一些模块具有更灵活的 promisify 函数,例如 es6-promisify。在 Node.js 中,有一个内置的 util.promisify
函数。
Promise 化是一种很好的方法,尤其是在使用 async/await
(本章后面会介绍 Async/await)时,但它并不能完全替代回调。
请记住,一个 Promise 可能只有一个结果,但一个回调在技术上可以被调用多次。
因此,Promise 化只适用于那些只调用回调一次的函数。后续的调用将被忽略。
评论
<code>
标记,要插入多行代码,请将其包装在<pre>
标记中,要插入 10 行以上的代码,请使用沙盒(plnkr、jsbin、codepen…)