在讲JavaScript异步编程之前我们先来了解并发和并行的区别。
并发和并行的区别
Erlang 之父 Joe Armstrong用一张图解释了并发和并行的区别:
并发是宏观概念,如果有任务AB,通过任务间的切换完成了这两个任务,我们可以称之为并发。
并行是微观概念,如果某个系统可以同时执行AB任务,我们就说这个系统是并行的。同时完成两个或多个任务的情况我们称之为并行。
回调函数
在JavaScript中处理异步返回结果的函数,我们称之为callback函数,常见的如下例子:
1
2
3
|
ajax(url, () => {
// do something
})
|
但如果多个请求存在依赖性,我们很容易写出这样的代码:
1
2
3
4
5
6
7
|
ajax(url, () => {
ajax(url1, () => {
ajax(url2, () => {
// ...
})
})
})
|
这就是回调地狱,这样写会导致两个问题:
- 代码耦合性高,牵一发而动全身,不利于维护
- 嵌套函数多,不利于处理错误
了解更多回调地狱的内容请参考: http://callbackhell.com/
那么,如何解决回调地狱的问题?
Generator + function*
Generator的特点是可以控制函数的执行,用法示例如下:
1
2
3
4
5
6
7
8
9
10
|
function *gen(x) {
let y = 2 * (yield (x + 1))
let z = yield y / 3
return (x + y + z)
}
let result = gen() // 返回迭代器
console.log(result.next()) // => { value: 6, done: false} 第一次next()会忽略传参,函数停留在yield(5 + 1)
console.log(result.next(12)) // => { value: 8, done: false} 第二次next要传入上一次yield的返回值,否则yield永远返回undefined。因为这里传的是12,所以y=2*12=24
console.log(result.next(13)) // => { value: 42, done: true} z = 13, x = 5, y = 24
|
Promise
Promise 的特点是什么,分别有什么优缺点?什么是 Promise 链?Promise 构造函数执行和 then 函数执行有什么区别?手写promise(内部如何实现)
Promise的特点
- Promise对象的状态不受外界影响 ,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态确定,就不会再变
Promise对象的缺点
- 构造Promise的时候,构造函数内部的代码是立即执行,无法取消
- 错误需要通过回调函数捕获
Promise 构造函数执行和 then 函数执行的区别
Promise构造函数是同步执行的,then方法是异步执行的
手写简易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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
const PENDING = 'pending'
const RESOLVE = 'resolve'
const REJECT = 'reject'
function MyPromise(fn) {
const _this = this
_this.state = PENDING
// value 用来保存resolve或reject传入的参数
_this.value = null
_this.resolveCallbacks = []
_this.rejectCallbacks = []
function resolve(value) {
// 只有pending状态的时候可以改状态
if(_this.state === PENDING) {
_this.state = RESOLVE
_this.value = value
// 执行保存起来的函数
_this.resolveCallbacks.map(cb => cb(value))
}
}
function reject(value) {
if(_this.state === PENDING) {
_this.state = REJECT
_this.value = value
_this.rejectCallbacks.map(cb => cb(value))
}
}
try{
fn(resolve, reject)
} catch(e) {
reject(e)
}
}
// then的实现
MyPromise.ptototype.then = function(onFullFilled, onRejected) {
const that = this
onFullFilled = type onFullFilled === 'function' ? onFullFilled : v => v
onRejected = type onRejected === 'function' ? onReject : e => throw e
// 当状态不为成功或失败,将函数保存起来,执行resolve或reject的时候再调用
if(that.status === PENDING) {
that.resolveCallbacks.push(onFullFilled)
that.rejectCallbacks.push(onRejected)
}
if(that.status === RESOLVE) {
onFullFilled(that.value)
}
if(that.status === REJECT) {
onRejected(that.value)
}
}
|
async/await
async/await是generate的语法糖,但async函数返回的是一个promise,所以也可以用then方法。async函数内部如果抛出错误,会导致promise对象变为reject状态:
1
2
3
4
5
6
7
8
9
10
11
|
async function test() {
throw new Error('报错了')
}
test.then(res => {
console.log(res)
}, error => {
console.log(error)
})
// Error:报错了
|
优点与缺点
- 优点:处理
then
的调用链,能够更清晰准确的写出代码,并且也能优雅地解决回调地狱问题
- 缺点:因为
await
将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await
会导致性能上的降低。
await内部原理
await
内部实现了 generator
,其实 await
就是 generator
加上 Promise
的语法糖,且内部实现了自动执行 generator
。