JavaScript学习笔记(二十七)期约(一)
- JavaScript时单线程事件循环模型,同步操作与异步操作时代码依赖的核心机制。
同步与异步
- 同步行为对应内存中顺序执行的处理器指令。
- 每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息。
- 异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。
期约基础
- 可以通过new操作符来实例化Promise。
- 创建期约时需要传入执行器函数作为参数。
1 | let p = new Promise(() => {}); |
期约状态机
期约是一个有状态的对象,包括下面三种状态之一:
- pending:待定
- fulfilled/resolved:兑现/解决
- rejected:拒绝
待定(pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。
无论落定为哪种状态都是不可逆的。
只要从待定转换为兑现或拒绝,期约的状态就不再改变。而且,也不能保证期约必然会脱离待定状态。
期约的状态是私有的,不能直接通过JavaScript检测到。
期约故意将异步行为封装起来,从而隔离外部的同步代码。
解决值、拒绝理由及期约用例
- 期约主要有两大用途:
- 抽象地表示一个异步操作。期约的状态代表期约是否完成。”待定”表示尚未开始或者正在执行中。“兑现”表示已经成功完成,而“拒绝”则表示没有成功完成。
- 期约封装的异步操作会实际生成某个值,而程序期待期约状态改变时可以访问这个值。相应地,如果期约被拒绝,程序就会期待期约状态改变时可以拿到拒绝的理由。
通过执行函数控制期约状态
- 执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。
- 控制期约状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为resolve()和 reject()。
- 调用resolve()会把状态切换为兑现;
- 调用reject ()会把状态切换为拒绝。另外,调用reject ()也会抛出错误。
1 | let p = new Promise((resolve, reject) => { |
Promise.resolve()
- 期约并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。
- 通过调用Promise.resolve()静态方法,可以实例化一个解决的期约。
1 | let p1 = new Promise((resolve, reject) => resolve()); |
- 使用这个静态方法,实际上可以把任何值都转换为一个期约。
1 | setTimeout(console.log, 0, Promise.resolve()); |
- Promise.resolve()是一个幂等方法,这个幂等性会保留传入期约的状态。
1 | let p = Promise.resolve(7); |
- 这个静态方法能够包装任何非期约值,包括错误对象,并将其转换为解决的期约。因此,也可能导致不符合预期的行为。
1 | let p = Promise.resolve(new Error('foo')); |
Promise.reject()
- Promise.reject ()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过try/catch 捕获,而只能通过拒绝处理程序捕获)。
- 拒绝的期约的理由就是传给Promise.reject()的第一个参数。
1 | let p = Promise.reject(3); |
- Promise.reject ()并没有照搬Promise.resolve()的幂等逻辑。如果传一个期约对象,则这个期约会成为它返回的拒绝期约的理由。
1 | setTimeout(console.log, 0, Promise.reject(Promise.resolve())); |
同步/异步执行的二元性
1 | try { |
第一个try/catch 抛出并捕获了错误,第二个try/catch 抛出错误却没有捕获到。乍一看这可能有点违反直觉,因为代码中确实是同步创建了一个拒绝的期约实例,而这个实例也抛出了包含拒绝理由的错误。这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这里就可以看出期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式的媒介。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 姚永坤的小窝!
评论