js基础之异步编程解决方案
异步编程是JS中必备的一部分。由于JS是单线程,如果全部事件都顺序执行会阻塞进程。所以一些耗时较长的事件采用异步方式进行。这里记录一些《深入浅出Node.js》笔记,有些地方没有碰到过,不理解,后续完善。
异步编程的一些问题:
- 异常处理
一般,使用try/catch/finally来进行错误处理
1 | try { |
现在有一个异步方法:
1 | const async = function (callback) { |
这里调用async()方法只能捕获async方法的异常,但是回调callback的异常却无法捕获
- 函数嵌套太深
1 | connection.query(sql, (err, result) => { |
- 阻塞代码
1 | // 这段代码虽然可以阻塞进程,但会持续占用CPU进行判断,性能不佳 |
- 多线程编程
- 异步转同步
异步编程解决方案
事件发布/订阅模式
1 | // 监听 |
事件发布/订阅没有同步、异步的概念,只要一发布、监听者立刻调用。
Promise/Defeered模式
Defeered: 延迟对象,暂不处理。
Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值。它的特点:
- Promise对象代表一个异步操作,只有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
- Promise只会从pending转化到fulfilled或者是rejected,不会逆转。转化成功后就不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
基本用法
创建一个Pormise实例:
1 | function asyncTime() { |
输出结果:
1 | promise start |
Promise实例化后立即执行,输出”promise start”,5秒后执行成功,输出”time down”。
有几点需要注意:
- resolve将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去。reject也可以将Promise对象的状态结束,但是是变成“失败”(即从 pending 变为 rejected)。
- then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。一般then只用来接收resolve状态。rejected状态由catch捕获执行。
- resolve()、reject()、then()和cacth()方法的返回值都是新的Promise对象。所以可以在后面接着使用then/catch,链式调用。
- 立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
1 | setTimeout(function () { |
链式调用
1 | doSomething() |
通常,一遇到异常抛出,promise链就会停下来,直接调用链式中的catch处理程序。
单独使用Promise.resolve()和Promise.reject()
Promise.resolve() 和 Promise.reject() 是手动创建一个已经resolve或者reject的promise快捷方法。以Promise.resolve()为例
1 | Promise.resolve('foo') |
- 如果Promise.resolve()参数是一个Promise对象,则不做任何修改,直接返回;
- 如果Promise.resolve()参数是一个不具有then方法的对象或根本就不是对象的参数,返回一个resolve状态的Promise对象
- 如果Promise.resolve()不带参数,返回一个resolve状态的Promise对象
- Promise.reject()返回reject状态的Promise对象
Primse.all()和Promise.race()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
1 | const p = Promise.all([p1, p2, p3]); |
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实>例。p的状态由p1、p2、p3决定,分成两种情况:
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
1 | const p = Promise.race([p1, p2, p3]); |
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
async函数
async函数跟Promise可以搭配使用。async函数有返回值时会返回一个Promise,后面可以跟then方法。
1 | // 基本用法,start → timer middle → (timer then) → end |
1 | // 错误示范,新建Promise会立即运行,而且没有返回一个Promise对象,await后面要么跟一个Promise对象(或有then方法的对象)要么跟个常量, |
异步的并发限制和超时控制
异步解决方案成熟的第三方库有async、Step等。
- Node中的异步调用有时需要控制并发数量,防止底层系统的性能出问题,一种思路是创建一个队列,每个异步调用顺序存入。设定最大并发数,如果当前活跃的异步调用数量小于最大并发数,直接取出执行,如果大于最大数量,则暂存在队列中,顺序取出调用。
- 超时控制可以给异步调用设置一个时间阈值,如果异步调用没有在规定时间内完成,则提示超时。
参考资料
《深入浅出Node.js》
ECMAScript6入门——Promise对象