期约是一种机制。
期约通常表示一个正在处理的、尚未结束的操作,当相关操作结束时,期约会通知系统。
期约机制是 ECMAScript 实现异步编程的重要组成部分。
目前,流行的期约机制的规范是规范 Promises/A+ 。
ES6 规范以规范 Promises/A+ 为范本,来实现期约。
ES6 新增类型 Promise ,来实现(支持)规范 Promises/A+ 。
主要参考资料:
- 《JavaScript 高级程序设计(第4版)》- P325(350/931)
期约状态机
期约主要有两个内部属性:
- 期约的状态
- 期约的值
期约的状态
期约有三种状态:
- 待定(pending)
- 兑现 / 履行(fulfilled)(也称作:解决(resolved))
- 拒绝(rejected)
期约的状态代表期约是否完成:
- 待定,表示期约尚未开始或者正在执行。
- 解决,表示期约已成功完成。
- 拒绝,表示期约没有成功完成。
期约的状态切换
期约的待定状态,被称为初始状态。
期约的解决状态、拒绝状态,被称为落定(settled)状态。
期约的状态可以从初始状态切换到落定状态,即:
- 期约从待定状态切换到解决状态。
- 期约从待定状态切换到拒绝状态。
期约从初始状态切换到落定状态的过程被称为落定。
期约的落定是不可逆的,只要期约切换到落定状态,期约的状态就不会再发生改变。
期约的值
当期约转换为落定状态时,期约内部会生成一个值:
- 当期约落定为解决时,这个值被简单地称为解决期约的值(value)。
- 当期约落定为拒绝时,这个值被称为拒绝期约的理由(reason)。
期约的值的默认值为 undefined 。
创建期约实例
通过类型 Promise 的构造函数 Promise() ,创建期约实例。
期约的构造函数 Promise() :
-
接收一个参数:
函数,期约的执行函数,提供两个参数:-
resolve ,函数,使期约实例的状态落定为解决。
接收一个参数(可选):任意值,作为解决期约实例的值。 -
reject ,函数,使期约实例的状态落定为拒绝。
接收一个参数(可选):任意值,作为拒绝期约实例的理由。
-
-
返回值:
期约。
关于执行函数:
在调用函数 resolve() 、reject() 之前,期约实例一直处于初始状态。
调用 resolve() 、reject() 中的任意一个,期约实例从初始状态切换为相应的落定状态。
期约实例处于落定状态后,再次调用 resolve() 、reject() 会静默失败,即期约实例落定后,不能改变期约实例的状态。
在执行函数中抛出错误,会导致构造函数 Promise() 返回一个拒绝期约,拒绝的理由为抛出的错误。
示例:
-
创建一个待定期约实例
const promise = new Promise( () => { console.log('Create a promise') } ) console.log(promise) // 输出: // Create a promise // Promise {<pending>}
-
创建一个期约实例,1s 后期约实例落定为解决期约
const promise_01 = new Promise( (resolve) => { setTimeout( () => { console.log(promise_01) resolve() // 落定为解决期约 }, 1000 ) } ) // 输出: // Promise {<fulfilled>: undefined}
-
创建一个期约实例,1s 后期约实例落定为拒绝期约
const promise_02 = new Promise( (_, reject) => { setTimeout( () => { reject() // 落定为拒绝期约 console.log(promise_02) }, 1000 ) } ) // 输出: // Promise {<rejected>: undefined}
-
创建一个期约实例,1s 后期约实例随机落定,并指定落定期约的内部值。
const promise_03 = new Promise( (resolve, reject) => { // 定义函数,对期约实例进行随机落定 function randomSettle() { /** * Math.random() ,返回区间为 [0, 1) 的随机数 * Math.round(x) ,对 x 进行四舍五入,并返回结果 */ const handle = Math.round(Math.random()) switch(handle) { case 0: resolve('is resolved') // 落定为解决期约,并指定解决期约的值 break case 1: reject('is rejected') // 落定为拒绝期约,并指定拒绝期约的理由 break default: break } } setTimeout( () => { randomSettle() console.log(promise_03) }, 1000 ) } ) // 可能的输出_01: // Promise {<fulfilled>: is resolved} // 可能的输出_02: // Promise {<rejected>: is rejected}
创建解决期约实例
通过期约的静态函数 Promise.resolve() ,创建解决期约实例。
期约的静态函数 Promise.resolve() :
-
功能:
创建一个解决期约实例。 -
接收一个参数(可选):
任意值,作为解决期约实例的值。 -
返回值:
对象,解决期约实例。
示例:
- 创建一个解决期约实例,并指定解决期约的值。
const promise = Promise.resolve('is resolved') console.log(promise) // 输出: // Promise {<fulfilled>: 'is resolved'}
如果给静态函数 Promise.resolve() 传入的参数是一个期约,那么静态函数 Promise.resolve() 返回的是该期约自身。
示例:
- 给静态函数 Promise.resolve() 传入一个期约。
const promise = Promise.resolve('is resolved') const nestedPromise = Promise.resolve(promise) console.log(promise === nestedPromise) // 输出: // true
创建拒绝期约实例
通过期约的静态函数 Promise.reject() ,创建拒绝期约实例。
期约的静态函数 Promise.reject() :
-
功能:
创建一个拒绝期约实例。 -
接收一个参数(可选):
任意值,作为拒绝期约实例的理由。 -
返回值:
对象,拒绝期约实例。
示例:
- 创建一个拒绝期约实例,并指定拒绝期约的理由。
const promise = Promise.reject('is rejected') console.log(promise) // 输出: // Promise {<rejected>: 'is rejected'}
期约的落定
开发者只能在期约的执行函数中落定期约。
ES6 没有向开发者提供更多其它的可以动态改变期约状态的 API 。
期约状态的变化主要由 JavaScript 引擎根据 ES6 的期约规范进行控制。
ES6 没有向开发者提供可以获取期约状态的 API
期约状态只能通过函数 console.log() 打印出来。
即开发者不能通过原生 API 将期约的状态作为判断条件来进行开发,说明期约规范本身不建议(允许)开发者这样使用期约。
响应期约的落定
通过期约的原型方法 then() 、catch()、finally() ,向期约实例添加处理程序,可以响应期约实例的落定。
期约的原型方法 then() 、catch()、finally() 都会排期任务,即向消息队列添加任务。
所以通过期约的原型方法 then()、catch()、fianlly() 添加的处理程序,都会被排期到消息队列中,被异步执行。
前置知识
关于期约:
ES6 的期约实现了接口 Thenable 。
接口 Thenable :
- 拥有一个方法:
then(callback) ,用于指定后续执行的程序。
示例:- 实现接口 Thenable 的对象。
const obj = { then(callback) { // ... callback('param') // ... } }
关于拒绝期约:
如果期约落定为拒绝期约,拒绝期约会抛出一个异步错误,异步错误的值为拒绝期约的理由。
拒绝期约抛出的异步错误,只能由该拒绝期约的拒绝处理程序捕获并处理。
Promise.prototype.then()
期约的原型方法 then() 用于为期约实例添加异步执行的解决处理程序、拒绝处理程序。
期约的原型方法 then() 会排期一个任务。
期约的原型方法 Promise.prototype.then() :
-
功能:
为期约实例添加异步执行的解决处理程序、拒绝处理程序。 -
接收两个参数:
-
函数(可选)
onResolved 处理程序(解决处理程序) ,在当前期约实例落定为解决期约后被调用。提供一个参数:value,当前期约实例落定为解决期约后的值。
-
函数(可选)
onRejected 处理程序(拒绝处理程序) ,在当前期约实例落定为拒绝期约后被调用,捕获并处理当前期实例约落定为拒绝期约后抛出的异步错误。
-