前言

没有前言…

没有异步处理

先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 假设有一个耗时的异步请求 ajax,在 2 秒后打印日志

function ajax () {
// do something...
setTimeout(() => {
console.log('Hello, Zavier Tang!')
}, 2000)
}
ajax()
// do something...
console.log('The end.')

// The end.
// Hello, Zavier Tang!

这里模拟了一个简单的异步网络请求,并在 2 秒后打印 “Hello, Zavier Tang!”,然后做一些处理(“do something”)后,打印结束 “The end.”。

结果是先打印的 “The end.”。

显然这并不是我们要的结果,”异步请求” ajax 中的 setTimeout 并没有阻塞代码的执行,而是直接执行了 console.log()

异步的解决方案

1. 传统(可怕)的 callback 回调地狱

同样是上面的异步网络请求,这里使用 callback 回调函数的方式解决 JavaScript 同步带来的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function ajax (fn) {
// do something...
setTimeout(() => {
console.log('Hello, Zavier Tang!')
fn()
}, 2000)
}
ajax(() => {
// do something...
console.log('The end.')
})

// Hello, Zavier Tang!
// The end.

这里我们直接把异步请求之后要做的一些操作做为回调函数,传递到 ajax 中去,并在异步请求结束后执行回调函数里的操作。

问题似乎已经解决了??但是,如果有多个异步请求,并且在每个异步请求完成后都执行一些操作,那代码就会像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function ajax (fn) {
// do something...
setTimeout(() => {
console.log('Hello, Zavier Tang!')
fn()
}, 500)
}

ajax(() => {
// do something...
console.log('The end 1.')
ajax(() => {
// do something...
console.log('The end 2.')
ajax(() => {
// do something...
console.log('The end 3.')
ajax(() => {
// do something...
console.log('The end 4.')
ajax(() => {
// do something...
console.log('The end 5.')
// ......
// ......
})
})
})
})
})

// Hello, Zavier Tang!
// The end 1.
// Hello, Zavier Tang!
// The end 2.
// Hello, Zavier Tang!
// The end 3.
// Hello, Zavier Tang!
// The end 4.
// Hello, Zavier Tang!
// The end 5.

看起来很吓人!!

如果是这样:请求1结束后进行请求2,然后还有请求3、请求4、请求5,并且请求2还可能依赖于请求1的数据,请求3依赖于请求2的数据,不停的一层一层嵌套,对于错误的处理也极不方便,所以称之为 callback 回调地狱。

2. 下一代异步解决方案:Promise

ES6 的 Promise 被称为 JS 异步的下一代解决方案。

关于 Promise:

  • 用于异步计算。
  • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
  • 可以在对象之间传递和操作 Promise,方便处理队列。

接下来使用 Promise 处理异步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function ajax (word) {
return new Promise((resolve) => {
// do something...
setTimeout(() => {
resolve('Hello ' + word)
}, 500)
})
}

ajax('请求1')
.then((word) => {
console.log(word)
console.log('The end 1.')
return ajax('请求2')
})
.then((word) => {
console.log(word)
console.log('The end 2.')
return ajax('请求3')
})
.then((word) => {
console.log(word)
console.log('The end 3.')
})
// .catch(() => {})

// Hello 请求1
// The end 1.
// Hello 请求2
// The end 2.
// Hello 请求3
// The end 3.

上面还是连续的异步请求,每次请求后打印日志。这样看起来比上面的回调地狱舒服多了,每个请求通过链式的调用,在最后可以对所有的请求进行错误处理。

但,似乎还并不是特别优雅。

3. 终极解决方案:async/await

关于 async/await 的简介:

  • async/await 是写异步代码的新方式,不同于以前的 callback 回调函数和 Promise。
  • async/await 是基于 Promise 实现的,不能用于普通的回调函数。
  • async/await 与 Promise 一样,是非阻塞的。
  • async/await 使得异步代码看起来像同步代码。

体验一下神奇的 async/await:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 接上面的代码
async function doAsync () {
const word1 = await ajax('请求1')
console.log(word1)
console.log('The end 1')

const word2 = await ajax('请求2')
console.log(word2)
console.log('The end 2')

const word3 = await ajax('请求3')
console.log(word3)
console.log('The end 3')
}
doAsync()

// Hello 请求1
// The end 1
// Hello 请求2
// The end 2
// Hello 请求3
// The end 3

这样看起来,更优雅了。没有任何括号,也没有 callback,没有 then,直接申明 async,使用 await 等待异步执行完成,看起来也更像是同步的代码。

总结

JavaScript 的异步编写方式,从 callback 回调函数到 Promise ,再到 async/await,只能说。。。

技术发展太快啦,赶紧学习吧!