首先,您几乎不想编写混合了回调和异步操作承诺的代码。如果您要转向 Promise 或引入一些 Promise,那么您可能希望将同一代码部分中的回调重构为 Promise。对于适当类型的操作,promise 相对于普通回调有很多优点,因此在已经在代码区域中工作时进行转换是非常值得的。
Promise 非常适合:
- 监控同步操作
- 只需要通知一次(通常是完成或错误)
- 协调或管理多个异步操作,例如排序或分支异步操作或同时管理多个正在进行的操作
- 从嵌套或深度嵌套的异步操作传播错误
- 准备好使用 async/await 的代码(或者现在通过转译器使用它)
- 适合 Promise 模型的操作只有三种状态:
pending
, fulfilled
and rejected
以及状态从哪里转换pending => fulfilled
或来自pending => rejected
然后就不能改变(单个单向转换)。
- 动态链接或链接异步操作(例如执行这两个异步操作,检查结果,然后根据中间结果决定执行哪些其他异步操作)
- 管理异步和同步操作的混合
- 自动捕获并向上传播异步完成回调中发生的任何异常(在普通回调中,这些异常有时会默默隐藏)。
普通回调对于 Promise 无法完成的事情很有用:
- 同步通知(例如回调
Array.prototype.map()
)
- 可能多次发生的通知(因此需要多次调用回调)。 Promise 是一次性设备,不能用于重复通知。
- 无法映射到待决、已完成、已拒绝单向状态模型的情况。
而且,我还要补充一点EventEmitter
混合。
EventEmitter 非常适合:
- 发布/订阅类型通知
- 具有事件模型的接口,特别是当事件可以多次发生时(如流)
- 当第 3 方代码想要参与或监控某些内容而无需使用 eventEmitter 以外的 API 时,就会出现松散耦合。没有API可供设计。只需公开一个 eventEmitter 并定义一些事件以及与之相关的数据即可。
关于将普通回调代码转换为 Promise 的注意事项
如果您的回调符合节点调用约定,回调作为最后一个参数传递并像这样调用callback(err, result)
,然后你会自动将父函数包装在一个承诺中util.promisify()
在 Node.js 中或者如果使用蓝鸟承诺图书馆 http://bluebirdjs.com/docs/api-reference.html, with Promise.promisify() http://bluebirdjs.com/docs/api/promise.promisify.html.
使用 Bluebird,您甚至可以立即 Promisify 整个模块(在 Node.js 调用约定中使用异步回调),例如:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.writeFileAsync("file.txt", data).then(() => {
// done here
}).catch(err => {
// error here
});
在 Node.js 版本 8+ 中
现在有util.promisify()
它将使用 Node.js 异步调用约定的异步函数转换为返回 Promise 的函数。
示例来自the doc: https://nodejs.org/api/util.html#util_util_promisify_original
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
// usage of promisified function
stat('.').then((stats) => {
// Do something with `stats`
}).catch((error) => {
// Handle the error.
});