一、Promisification 是一种很好的方法,特别是在你使用async/await的时候,但不是回调的完全替代。
二、一个 promise 可能只有一个结果,但从技术上讲,一个回调可能被调用很多次。
1、promisification 仅适用于调用一次回调的函数。进一步的调用将被忽略。
三、“Promisification” 是用于一个简单转换的一个长单词。它指将一个接受回调的函数转换为一个返回 promise 的函数。
四、由于许多函数和库都是基于回调的,因此,在实际开发中经常会需要进行这种转换。因为使用 promise 更加方便,所以将基于回调的函数和库 promisify 是有意义的。(译注:promisify 即指 promise 化)
| ☆【示例】在回调一章中我们有loadScript(src, callback)。```javascript 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); }
// 用法: // loadScript(‘path/script.js’, (err, script) => {…})
1、该函数通过给定的src加载脚本,然后在出现错误时调用callback(err),或者在加载成功时调用callback(null, script)。这是大家对于使用回调函数的共识,我们之前也学习过。<br />2、现在,让我们将其 promisify 吧。 |
| --- |
| ☆【示例】我们将创建一个新的函数loadScriptPromise(src),与上面的函数作用相同(加载脚本),只是我们创建的这个函数会返回一个 promise 而不是使用回调。<br />1、换句话说,我们仅向它传入src(没有callback)并通过该函数的 return 获得一个 promise,当脚本加载成功时,该 promise 将以script为结果 resolve,否则将以出现的 error 为结果 reject。<br />2、代码实现如下:```javascript
let loadScriptPromise = function(src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) reject(err);
else resolve(script);
});
});
};
// 用法:
// loadScriptPromise('path/script.js').then(...)
3、正如我们所看到的,新的函数是对原始的loadScript函数的包装。新函数调用它,并提供了自己的回调来将其转换成 promise resolve/reject。
4、现在loadScriptPromise非常适用于基于 promise 的代码了。如果我们相比于回调函数,更喜欢 promise,那么我们将改用它。 |
| —- |
五、在实际开发中,我们可能需要 promisify 很多函数,所以使用一个 helper(辅助函数)很有意义。我们将其称为promisify(f):它接受一个需要被 promisify 的函数f,并返回一个包装(wrapper)函数。
| ☆【示例】```javascript function promisify(f) { return function (…args) { // 返回一个包装函数(wrapper-function) () return new Promise((resolve, reject) => { function callback(err, result) { // 我们对 f 的自定义的回调 (*) if (err) { reject(err); } else { resolve(result); } }
args.push(callback); // 将我们的自定义的回调附加到 f 参数(arguments)的末尾
f.call(this, ...args); // 调用原始的函数
});
}; }
// 用法: let loadScriptPromise = promisify(loadScript); loadScriptPromise(…).then(…);
1、代码看起来可能有些复杂,但其本质与我们在上面写的那个是一样的,就是将loadScript函数 promisify。<br />2、调用promisify(f)会返回一个f(*)的包装器。该包装器返回一个 promise,并将调用转发给原始的f,并在我们自定义的回调(**)中跟踪结果。<br />3、在这里,promisify假设原始函数期望一个带有两个参数(err, result)的回调。这就是我们最常遇到的形式。那么我们自定义的回调的格式是完全正确的,在这种情况下promisify也可以完美地运行。<br />4、但是如果原始的f期望一个带有更多参数的回调callback(err, res1, res2, ...),该怎么办呢? |
| --- |
| ☆【示例】我们可以继续改进我们的辅助函数。让我们写一个更高阶版本的promisify。<br />- 当它被以promisify(f)的形式调用时,它应该以与上面那个版本的实现的工作方式类似。<br />- 当它被以promisify(f, true)的形式调用时,它应该返回以回调函数数组为结果 resolve 的 promise。这就是具有很多个参数的回调的结果。<br />```javascript
// promisify(f, true) 来获取结果数组
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // 我们自定义的 f 的回调
if (err) {
reject(err);
} else {
// 如果 manyArgs 被指定,则使用所有回调的结果 resolve
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
}
// 用法:
f = promisify(loadScript, true);
f('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js').then(arrayOfResults => ..., err => ...);
1、正如你所看到的,它与上面那个实现基本相同,只是根据manyArgs是否为真来决定仅使用一个还是所有参数调用resolve。
2、对于一些更奇特的回调格式,例如根本没有err的格式:callback(result),我们可以手动 promisify 这样的函数,而不使用 helper。
3、也有一些具有更灵活一点的 promisification 函数的模块(module),例如es6-promisify。在 Node.js 中,有一个内建的 promisify 函数util.promisify。 |
| —- |