0%

关于js异步编程的一点研究

前言

最近在看阮老师的JavaScript基础和es6文档,其中关于异步的处理,我觉得有必要整理一下。这有助于我逐步改善自己的编程风格,提升代码质量。

Promise

文档传送门:Promise 对象
promise已经是现如今最广泛使用的异步解决方案,几乎已经没有人去使用回调来解决异步问题。callback最广为人知的问题是回调地狱,因此promise应运而生。然鹅,promise的链式调用也没有从根本上解决这个问题,.then()的调用只是换了一种写法;同时,promise的错误处理也是一个大麻烦。
Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

1
const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

先来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function sleep(t){
console.log(t)
return new Promise(resolve => {
setTimeout(()=>{
console.log(t+'运行结束')
resolve()
},t)
})
}
// 这时我需要调用sleep两次,这两次调用需要同时进行,并在两次调用都返回值后打印总运行时间
function f(){
const start = new Date().getTime()
Promise.all([sleep(2000),sleep(1000)]).then(res=>{
const end = new Date().getTime()
const time = (end - start)/1000
console.log('总共运行时间为'+time)
})
}
f()
// > 2000
// > 1000
// > "1000运行结束"
// > "2000运行结束"
// > "总共运行时间为2.001"

这个方法可以应用在多个http需要同时请求时的情况。需要注意的是,这时的错误处理需要分情况处理:如果原Promise实例有自己的catch处理,则不会触发all的catch,反之会触发all的catch
Promise.race()
这个方法与all类似,区别是只要有一个Promise先改变了状态(resolve,reject皆可),就会传回回调。这个方法暂时没有想到什么应用方向(感觉它与高并发的秒杀业务流程类似)
Promise.any()
Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。
这是es2021引入的新方法。谨慎使用

async/await

文档传送门:async 函数

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

文档中提到了 Generator 函数,这个函数我粗略的看了一下,它的异步应用似乎只是它延申出的应用而已,本身是实现了函数的分步执行。在有了async/await之后,似乎并不需要它来实现异步了。参考:ES6+中的Generator 函数有何特别之处?
基本使用
作为一个语法糖,可以将上述代码简化:

1
2
3
4
5
6
7
8
async function f(){
const start = new Date().getTime()
// 少了.then()的链式调用,代码清晰了不少
await Promise.all([sleep(2000),sleep(1000)])
const end = new Date().getTime()
const time = (end - start)/1000
console.log('总共运行时间为'+time)
}

sleep()函数也可以用async/await写法,除了写法不同,实际运行效果一样:

1
2
3
4
5
6
7
8
9
async function sleep(t){
console.log(t)
await new Promise(resolve => {
setTimeout(()=>{
console.log(t+'运行结束')
resolve()
},t)
})
}

错误处理
如果使用多个await处理异步函数,在前一个函数返回reject时,之后不会执行。为了防止出错,需要将其放在try…catch代码块之中。
一个文档中的使用例子,可以实现多次重复尝试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}

test();

持续学习

关于js的异步编程,我自认还没有完全了解,只是了解了写法和部分原理,只有持续学习,看看在实际应用中还是否会遇上问题。

几篇不错的文章(可能会更新):

  1. 100 行代码实现 Promises/A+ 规范
  2. 小哥哥小姐姐,来尝尝 Async 函数这块语法糖