一、异步编程
1.异步和同步
同步行为对应内存中顺序执行的处理器指令;异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行
2.以往的异步编程模式
早期的js中,只支持定义回调函数来表明异步操作的完成。串联多个异步操作是一个常见的问题,通常需要深度嵌套回调函数(回调地狱)
2-1)异步返回值
广泛接受的一个策略是给异步操作一个回调,回调中包含要使用异步返回值的代码
2-2)失败处理
添加成功回调和失败回调
2-3)嵌套异步回调
异步返回值又依赖另一个异步返回值
二、期约
期约是对一个尚不存在的结果的一个替身
1.Promises/A++规范
早期的期约机制在jQuery和Dojo中以Deferred API等形式出现;
2010年,CommonJS项目实现的Promises/A规范日益流行;
2012年,Promises/A+组织分叉了CommonJS的Promises/A建议,制定了Promises/A+规范;
ES6增加了对Promises/A+规范的完善支持,即Promise类型
2.期约基础
2-1)期约状态机
期约可能处于pending、 fulfilled和rejected三种状态之一,且从pending落定为其他任意一种状态的过程都不可逆
期约的状态是私有的,不能直接通过JS检测到;也不能被外部JS代码修改(故意将异步行为封装起来,从而隔离外部同步代码)
2-2)解决值、拒绝理由及期约用例
期约有两大用途:a)抽象地表示一个异步操作;b)另外一些情况,期约封装的异步操作会实际生成某个值,程序期待期约状态变化时可以访问这个值。
2-3)通过执行函数控制期约状态
内部操作在期约的执行器函数中完成,执行器函数的两个职责:a)初始化期约的异步行为;b)控制状态的最终转化
执行器函数是同步执行的
为避免期约卡在待定状态,可以添加一个定时退出功能
2-4)Promise.resolve()
可以实例化一个状态为fulfilled的期约;这个方法是幂等的
2-5)Promise.reject()
会实例化一个拒绝的期约,并抛出一个异步错误(这个错误不能被try/catch捕获,只能通过拒绝处理程序捕获);
这个函数不是幂等的
2-6)同步/异步执行的二元性
期约的异步特性:他们是同步对象(在同步模式中使用),但也是异步执行模式的媒介
代码一旦开始以异步模式执行,则唯一与之交互的方法就是使用异步结构----期约的方法
3.期约的实例方法
连接外部同步代码和内部异步代码之间的桥梁
3-1)实现Thenable接口
ECMAScript暴露的异步结构中,任何对象都有一个then方法
3-2)Promise.prototype.then()
接受两个可选参数,onResolved处理程序和onRejected处理程序,如果只想传onRejected处理函数,则需要在onResolved传入undefined
3-3)Promise.prototype.catch()
用于给期约添加拒绝处理程序,只接受一个参数,onRejected处理程序
3-4)Promise.protototype.finally()
用于给期约添加onFinally处理程序,无论状态转为fulfilled和rejected,都会执行
3-5)非重入期约方法
“非重入特性”:
当期约进入落定状态时,与该状态相关的处理程序只是会排期,而非立即执行。跟在处理程序之后的同步代码一定会在处理程序之前执行。
非重入特性适用于onResolved/onRejected/catch/finally处理程序
3-6)邻近处理程序的执行顺序
如果给期约添加了多个处理程序,当状态变化时,相关处理程序会依次执行,无论是then/catch/finally
3-7)传递解决值和拒绝理由
分别作为resolve/reject的第一个参数往后传,这些值会传给各自的处理程序,作为onResolved/onRejected的唯一参数
3-8)拒绝期约与拒绝错误处理
在期约中抛出错误时,实际是从消息队列中异步抛出的,并不会组织运行时继续执行同步命令
4、期约连锁与期约合成
4-1)期约连锁:
把期约逐个逐个串联起来,因为每个期约都有自己的then/catch/finally方法,这些方法都会返回一个新的期约,而新的期约又有自己的实例方法。
这种连缀的方法调用就可以构成所谓的“期约连锁”
4-2)期约图:期约连锁可以构建有向非循环图的结构
4-3)Promise.all()与Promise.race()
Promise.all():接受一个可迭代对象作为参数,返回一个新的期约。可迭代对象中的元素会通过Promise.resolve()转为期约
合成的期约会在所有的期约都解决后解决
如果有期约拒绝,则会将第一个拒绝的拒绝理由作为理由返回
Promise.race():无论是拒绝还是解决,只要第一个落定就会包装其解决值或者拒绝理由并返回
5.期约扩展
很多第三方库实现中具备而ECMAScript规范中却未涉及的两个特性:期约取消和进度追踪
(太妙了,见于红皮书P346)
期约取消:用Kevin Smith提到的“取消令牌”,生成的令牌实例提供一个接口,利用这个接口,可以取消期约;同时提供一个期约实例,用于触发取消后的操作并求值取消状态
期约进度通知:扩展Promise类,实现notify()方法
三、异步函数
ES8新增async/await,旨在利用异步结构组织代码的问题
1.异步函数async/await
1-1).async 声明异步函数,可以用在函数声明、函数表达式、箭头函数和方法上
异步函数如果使用return返回了值,则这个值会被Promise.resolve()包装
异步函数的返回值期待一个实现thenable接口的对象,如果是则可以交给then()处理程序解包;如果不是,则用Promise.resolve()包装
如果在异步函数中抛出错误,会返回拒绝的期约
1-2).await 可以暂停异步函数代码的执行,等待期约解决
await期待一个实现了thenable接口的对象,如果是,则由await解包;如果不是,就被当做已解决的期约
抛出同步的错误,则会返回拒绝的期约
1-3).await的限制
await必须在异步函数中使用;
定义并立即执行异步函数是没问题的;
2.停止和恢复执行
JavaScript运行时碰到了await关键字时,会记录在哪暂停执行。等到await右边的值可用,JavaScript运行时会向消息队列推送一个任务,这个任务会恢复异步函数的执行
3.异步函数策略
3-1)实现sleep(), 在程序中加入非阻塞的暂停
3-2)利用平行执行
如果顺序不是必须保证的,可以先一次性初始化所有期约,然后分别等待结果。
虽然期约不是顺序执行的,但是await按顺序接受了结果
3-3)串行执行期约
3-4)栈追踪与内存管理
JavaScript引擎在创建期约时,会尽可能保存完整的调用栈。异步函数比期约节省内存。