j**ascript 应用的首选实现。
承诺是 j**ascript 开发中的一个重要概念。 根据 Promise A+ 规范的定义,promise 表示异步操作的最终结果。 与 promise 交互的方式是通过其 then(onfulfilled, onrejected) 方法,该方法注册处理 promise 最终结果的方法。 promise 表示的异步操作可能会成功,也可以失败。 成功后,onfulfilled 方法接受一个值作为最终结果; 失败时,方法 onrejected 接受操作失败的原因。
承诺可以处于以下三种状态之一:待处理,这意味着与承诺对应的异步操作仍在进行中; Fulfilled 表示异步操作已成功完成; “拒绝”表示无法完成异步操作。 promise 对象的 then 方法接受两个参数,分别是满足状态和拒绝状态。 满足状态方法的第一个参数是 promise 的最终结果值; Rejected 方法的第一个参数是 promise 被拒绝的原因。
Promise 是 ECMAScript 6 规范的一部分,并且已经有许多第三方库提供与 promise 相关的实现。 Promise A+ 规范只是定义了 then 方法,该方法旨在实现不同实现之间的互操作性,重点关注 promise 的一般用例。 ecmascript 6 规范还定义了 promise 的其他方法,包括 promiseall、promise.race、promise.拒绝和承诺决议等。 可以在 nodejs 和浏览器环境中使用不同的 promise 实现。 本文中介绍的 Bluebird 是一种功能丰富且高性能的承诺实现。
根据操作环境的不同,可能会有不同的安装方法。 在 nodejs 应用程序中,您可以直接使用 npm install bluebird 安装它。 在浏览器应用程序中,您可以直接发布 jascript 文件的版本,也可以使用 bower 安装它。 本文中的示例基于 nodejs 环境,对应的版本为 LTS 89.版本 4,使用 ECMASCRIPT 6 的语法。 在 nodejs 环境中,你可以通过 const promise = require('bluebird') 开始使用 bluebird 提供的 promise 对象。
使用 Bluebird 作为 promise 实现时的第一个问题是如何创建 promise 对象。 事实上,大多数时候,你不需要明确地创建承诺。 许多第三方库已经返回包含 then 方法的 promise 对象或 thenable 对象,这些对象可以直接使用。
Bluebird 的一个有用功能是将不使用 promise 的现有 API 包装到返回 promise 的新 API 中。 NodeJS 的标准库 API 和许多第三方库 API 大多使用 **method 模式,即在执行异步操作时,需要传入一个 **方法来接受操作的执行结果和可能的错误。 对于这样的方法,Bluebird可以很容易地将它们转换为承诺的形式。
例如,在 nodejs 中读取文件readfile 方法,常见的使用模式如清单 1 所示。 使用该方法的问题在于,它可能导致嵌套层过多,从而产生所谓的回调地狱。
清单 1在 NodeJS 中 FSreadfile 方法的基本用法。
const fs = require('fs'),path = require('path');fs.readfile(path.join(__dirname, 'sample.txt'), 'utf-8', (err, data) => else })
蓝鸟的承诺promisifyall 方法为对象属性中的所有方法创建 promise 版本。 这些新创建的方法的名称在现有方法的名称后加上后缀“async”。 清单 1 中的实现可以更改为清单 2 中的承诺形式。 新的 readfileasync 方法对应于现有的 readfile 方法,但返回值是 promise 对象。 除了 readfile 之外,FS 中的其他方法也有相应的 async 版本,比如 writefileasync 和 fstatasync。
清单 2使用承诺promisifyall 来转换方法。
const promise = require('bluebird'),fs = require('fs'),path = require('path');promise.promisifyall(fs);fs.readfileasync(path.join(__dirname, 'sample.txt'), 'utf-8').then(data => console.log(data)).catch(err => console.error(err));
如果不想自动将对象的所有方法转换为 promise 的形式,则可以使用 promise承诺转换单个方法,例如 Promisepromisify(require(“fs”).readfile)。对于 nodejs 格式的方法,可以使用 promiseFromCallback 将其转换为承诺。 该方法的结果确定 promise 的状态。
对于现有值,可以使用 promiseresolve 方法将其转换为具有 Satisfied 状态的 promise 对象。 再次,使用承诺reject 方法可以根据给定的拒绝原因创建状态为 Rejected 的 promise 对象。 这两种方法都是创建 promise 对象的快速方法。
如果这些方法都不能满足创建 promise 的需要,则可以使用 promise 构造函数。 构造函数的参数是一个方法,它接受两个方法作为参数,分别用于将 promise 标记为满足或拒绝。 在清单 3 中,创建的 promise 的状态取决于生成的随机数是否大于 05。
清单 3创建承诺
const promise = require('bluebird');const promise = new promise((resolve, reject) => else `)promise.then(console.log).catch(console.error);
在前面的示例中,我们看到了 promise 对象的基本用法。 其中,then 方法用于添加处理 promise 结果的方法。 您可以为“满意”和“拒绝”状态添加方法。 点差方法的工作方式与那时类似。 当 promise 的结果值为数组时,可以使用 spread 方法将数组的元素展平为 ** 方法的不同参数。 在清单 4 中,promise 的值是一个包含 3 个值的数组,分别对应于处理方法的 3 个参数中的每一个。
清单 4传播使用示例。
promise.resolve([1, 2, 3]).spread((v1, v2, v3) => console.log(v1 + v2 + v3));
catch 方法用于添加被拒绝的状态。 有两种方法可以使用 catch 方法。 第一种方式只添加**方法,捕获所有错误情况; 第二种方法是使用错误对象类型或谓词来筛选错误。 error 方法的工作方式与 catch 类似,但 error 仅处理实际的 error 对象。 j**ascript 中的 throw 语句可以抛出任何值,而不一定是错误对象。 throw 抛出的任何值都将被捕获,但只有错误对象才会被 error 处理。 在清单 5 中,没有调用错误添加的处理程序。
清单 5错误使用示例。
promise.reject('not an error').error(err => console.log('should not appear'));
最后,无论 promise 的最终状态如何,都将调用该方法。 清单 6 中的 Catch 将只捕获 TypeError,而 finally 中的逻辑将始终被调用。 清单 6抓住最后使用示例。
promise.reject(new typeerror('some error')).catch(typeerror, console.error).finally(()=> console.log('done'));
对于 promise 对象,您可以使用它提供的方法查看其内部状态。
iSpending 检查承诺的状态是否正在进行中。 isfulfilled 检查是否满足 promise 的状态。 IsRejected 检查 promise 的状态是否为“已拒绝”。 iscancelled 检查承诺是否已取消。 value 获取承诺得到满足的结果。 获得承诺被拒绝的原因。 您可以使用 cancel 方法取消 promise,表示您不再对 promise 的结果感兴趣。 取消 promise 时,不会调用其任何方法。 取消功能默认禁用,需要使用 promiseconfig 以启用该功能。 需要注意的是,取消 promise 仅表示 promise 的方法不会被调用,并且不会自动取消正在进行的异步操作,例如不会自动终止正在进行的 XHR 请求。 如果需要添加自定义取消逻辑,可以添加第三个参数 oncancel,在取消 promise 时注册方法。
清单 7使用承诺的取消功能。
promise.config();promise.delay(1000, 'hello').then(console.log).cancel();
前面的例子是针对一个承诺的。 在实践中,通常需要与多个承诺进行交互。 例如,如果我们读取单个文件并返回一个 promise 对象,则需要等待多个文件成功后再执行其他操作。 promise.所有人都可以接受可迭代的参数并返回新的承诺。 只有当可迭代对象中包含的所有 promise 对象都得到满足时,生成的 promise 才会得到满足; 任何 promise 中的错误都将导致 promise 被拒绝的结果。 新 promise 的最终结果是一个数组,其中包含可迭代对象中 promise 的结果。 任何值都可以包含在可迭代对象中。 如果该值不是 Promise,则其值将直接显示在结果中,无需等待 Promise 完成。 在清单 8 中,我们使用 promise全部等待 3 个读取文件操作完成。
清单 8 promise.所有用法示例。
promise.all([fs.readfileasync(path.join(__dirname, '1.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '2.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '3.txt'), 'utf-8')])then(results => console.log(results.join(', ')))catch(console.error);
在许多情况下,您需要将可迭代对象中的对象转换为 Promise,并等待这些 Promise 完成。 对于此类方案,可以使用 promisemap。map 方法的第一个参数是可迭代对象,第二个参数是转换为 promise 的方法,第三个参数是可选的配置对象,可用于使用属性并发来控制同时运行的 promise 数量。 在清单 9 中,我们使用 promiseMAP 将包含文件名称的数组转换为读取文件内容的 Promise 对象,然后等待读取操作完成。 功能与清单 8 相同,但实现更简单。
清单 9 promise.map的使用示例。
promise.map(['1.txt', '2.txt', '3.txt'],name => fs.readfileasync(path.join(__dirname, name), 'utf-8')).then(results => console.log(results.join(', ')))catch(console.error);
promise.地图系列的作用与承诺的作用相同地图是相同的,只是地图系列按可用顺序遍历每个元素。
当您有多个 promise 时,如果只需要等待其中一些 promise 完成,则可以使用 promisesome 并指定要完成的承诺数量。 在清单 10 中,您只需要等待 2 个文件读取操作完成。
清单 10 promise.一些使用的例子。
promise.some([fs.readfileasync(path.join(__dirname, '1.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '2.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '3.txt'), 'utf-8')],2).then(results => console.log(results.join(', ')))catch(console.error);
promise.Any 等同于使用 promisesome 并将数量设置为 1,但承诺any 的结果不是长度为 1 的数组,而是特定的单个值。 promise.种族的运作方式与任何种族类似,但种族的结果可能是被拒绝的承诺。 因此,推荐的做法是使用任何。
promise.筛选器可以等待多个承诺完成并筛选结果。 它实际上的工作方式与承诺相同map,然后使用数组的 filter 方法进行筛选。 在清单 11 中,promise筛选器用于筛选出内容长度为 1 或更小的文件。
清单 11 promise.过滤器的使用示例。
promise.filter([fs.readfileasync(path.join(__dirname, '1.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '2.txt'), 'utf-8'),fs.readfileasync(path.join(__dirname, '3.txt'), 'utf-8')],value => value.length > 1).then(results => console.log(results.join(', ')))catch(console.error);
promise.每个依次处理可迭代对象中的元素。 处理方法还可以返回一个 promise 对象来表示异步处理。 只有在处理完前一个元素后,才会处理下一个元素。
promise.reduce 将多个 promise 的结果减少为单个值。 它的工作方式类似于数组的reduce方法,但可以处理promise对象。 在清单 12 中,第一个参数是一个挂起文件名的数组; 第二个参数是累加操作的方法,total表示当前累加值,用于将当前读取的文件长度相加; 第三个参数是累积的初始值。 从**中可以看出,累积操作也可以返回一个 promise 对象。 promise.减少等待 promise 完成的等待时间。
清单 12 promise.reduce的使用示例。
promise.reduce(['1.txt', '2.txt', '3.txt'],(total, name) => ,0).then(result => console.log(`total size: $catch(console.error);
promise.all 用于处理动态数量的 promise、promise联接用于处理固定数量的不相关承诺。
promise.method 用于封装方法,以便它返回 promise。 promise.Try 用于封装可能导致问题的方法调用,并根据调用结果确定 promise 的状态。
如果使用Promise中需要释放的资源,例如数据库连接,则不容易确保这些资源被释放。 一般做法是将资源释放逻辑添加到 finally。 但是,当承诺相互嵌套时,很容易获得未释放的资源。 Bluebird 提供了一种更强大的方式来管理资源,使用处置程序和承诺using 方法。
资源释放器由一个 disposer 对象表示,该对象是通过 promise 的 disposer 方法创建的。 create-time 参数是用于释放资源的方法。 该方法的第一个参数是资源对象,第二个参数是 promise使用生成的 promise 对象。 处置器对象可以传递给 promise用于确保其资源释放逻辑得到执行。
在清单 13 中,connection 表示数据库连接,db 表示数据库。 DB 的 Connect 方法创建一个返回 Promise 对象的数据库连接。 Connection 的查询方法表示数据库查询的执行,返回的结果也是一个 Promise 对象。 Connection 的 Close 方法关闭数据库连接。 使用时,在 connect 方法返回的 promise 对象之上使用 disposer 创建 disposer 对象,对应的资源释放逻辑是调用 close 方法。 然后你就可以传递承诺了using 使用连接对象。
清单 13承诺。
const promise = require('bluebird');class connection close() class db }const disposer = new db().connect().disposer(connection => connection.close())promise.using(disposer, connection => connection.query())then(console.log).catch(console.error);
promise.延迟返回的 promise 在指定时间后转换为满足状态,结果为指定值。 promise.超时用于向现有 promise 添加超时。 如果现有 promise 未在指定的超时期限内完成,则生成的 promise 将生成 timeouterror 错误。
在清单 14 中,第一个延迟方法在 1 秒后输出 hello。 第二种方法引发 TimeOutError 错误,因为 1 秒的延迟超过了 500 毫秒的超时。
清单 14 promise.延迟和承诺超时的使用示例。
promise.delay(1000, 'hello').then(console.log);promise.delay(1000, 'hello').timeout(500, 'timed out').then(console.log).catch(console.error);
该承诺还包括一些实用的方法。 Tap 和 tapcatch 分别用于查看 promise 中的结果和发生的错误。 这两种方法的处理不影响承诺的结果,适用于日志记录。 call 用于调用 Promise Result 对象中的方法。 get 用于获取 Promise 结果对象中属性的值。 return 用于更改 promise 的结果。 throw 用于抛出错误。 CatchReturn 用于在捕获错误后更改 promise 的值。 CatchThrow 用于捕获错误,然后抛出新错误。
*清单 15 中给出了这些实用方法的使用示例。 第一个语句使用 tap 输出 promise 的结果。 第二条语句使用 call 调用 sayhi 方法,并将该方法的返回值作为 promise 的结果。 第三个语句使用 get 来获取作为 promise 结果的属性 b 的值。 第四个语句使用 return 来更改 promise 的结果。 第五条语句使用 throw 抛出错误,使用 catch 来处理错误。 第六条语句使用 catchreturn 来处理错误并更改 promise 的值。
清单 15使用实用方法的例子。
promise.resolve(1).tap(console.log).then(v => v + 1).then(console.log);promise.resolve().call('sayhi').then(console.log);promise.resolve().get('b').then(console.log);promise.resolve(1).return(2).then(console.log);promise.resolve(1).throw(new typeerror('type error')).catch(console.error);promise.reject(new typeerror('type error')).catchreturn('default value').then(console.log);
虽然可以使用 promise 捕获来捕获和处理错误,但在许多情况下,由于程序本身或第三方库的问题,仍然有可能未收到错误。 未捕获的错误可能导致 nodejs 进程意外退出。 Bluebird 提供全局和本地错误处理机制。 Bluebird 触发与拒绝 promise 相关的全局事件,分别是 unhandledrefusal 和 rejectionhandled。 您可以添加全局事件处理程序来处理这两种类型的事件。 对于每个 promise,您可以使用 onpossiblyunhandledrejection 方法为未处理的拒绝错误添加默认处理方法。 如果您不关心未处理的错误,则可以使用 suppressUnhandledRejections 忽略错误。
在清单 16 中,promise 的拒绝错误不被处理,而是由全局处理器处理。
清单 16错误处理示例。
promise.reject(new typeerror('error')).then(console.log);process.on('unhandledrejection', (reason, promise) => console.error(`unhandled $`
Promise 是与 j**ascript 应用程序开发中的异步操作相关的重要抽象。 作为一个流行的 promise 库,Bluebird 提供了许多与 promise 相关的有用功能。 本文详细介绍了 Bluebird 的重要功能,包括如何创建 promise 对象、使用 promise、使用集合、promise 实用程序和错误处理。