JavaScript异步编程

JavaScript异步编程

JavaScript异步编程的进化历程:callback -> Promise -> Generator -> async/await

异步的意思是该代码与其他代码的关系是异步执行的,而在该代码自己内部是同步执行的。

js的异步机制:

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

回调函数(callback)

回调函数是js中常见的异步编程方式,比如定时器:

1
2
3
4
5
6
7
8
9
// 会先执行f1的代码,再将传入的f2作为回调函数执行。
function f1(callback) {
// f1 code
setTimeout(() => {
callback();
}, 0);
}

f1(f2);

除了 setTimeout 之外,还有 XMLHttpRequest 也使用了回调函数编写异步任务。

事件监听(EventListener)

比如由f1运行一段任务,任务完成后触发一个事件。
f2监听这个事件,监听到了就运行任务。

1
2
3
4
5
6
7
8
9
10
11
12
// 给f1绑定事件和触发函数f2
f1.on('event_name', f2);

// 运行f1的任务,完成后触发事件
function f1() {
setTimeout(() => {
// f1 code

// 触发事件
f1.trigger('event_name');
}, 0);
}

发布/订阅(publish/subscribe)

有一个信号中心,生产者发布信号(publish)到信号中心,消费者订阅信号中心的信号(subscribe)。

消费者还可以取消订阅信号(unsubscribe)。

Promise

Promise 可以将回调套娃的代码变为扁平的顺序结构。

Promise对象的状态只能从pending到resolve或者从pending到reject。

注意: then() 接受两个参数,第一个参数是resolve时调用的函数,第二个参数是reject时调用的函数。

比如:

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
// Promise的参数是一个函数,这个函数会被异步执行
// Promise中的resolve()和reject()用于向then传递参数
new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(1);
resolve();
}, 1000);
}).then(function() {
// then中可以使用return向后面的then传递参数
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(2);
resolve();
}, 4000);
});
}).then(function() {
setTimeout(function() {
console.log(3);
}, 3000);
}).catch(function() {
// 捕获异常,前面的运行中有任何异常都会直接跳到这里
}).finally(function() {
// 最终运行的代码
});

// 1
// 2
// 3

jQuery异步编程API:

基于Promise的when, then, done, fail

参考我之前写的:jQuery的when, then, done, fail

Generator

Generator 用于创建迭代器(iterator)。

关于iterator:

  • iterator为不同的可迭代的对象提供统一的访问接口。
  • 可以按照次序迭代可迭代对象。
  • 可以用for…of语句迭代。
  • iterator内有next方法,next内有value(当前迭代的值)和done(是否迭代完成)属性。

创建迭代器:

1
2
3
4
5
6
let arr = ['a', 'b', 'c'];

let iter = arr[Symbol.iterator]();

// 调用next方法
iter.next();

创建Generator并使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建Generator,执行后返回一个iterator对象
function* createGenerator() {
yield 1;
yield 2;
yield 3;
}

// 返回值是一个iterator
let iter = createGenerator();
iter.next(); // {value: 1, done: false}
iter.next(); // {value: 2, done: false}
iter.next(); // {value: 3, done: false}
iter.next(); // {value: undefined, done: true}

Generator可以在运行过程中使用 yield 多次return,故而可以异步编程。

注意:Generator需要配合 yield* 使用。

异步函数(async/await)

async/await是JavaScript异步编程的终极解决方案。

异步函数的返回值是一个Promise对象。

在异步函数内部,可以使用 await 命令等待一个Promise对象处理的结果(await也可以等待其他数据类型的字面量),再顺序往下执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function asyncFunction() {
return new Promise((resolve) => {
setTimeout(function() {
console.log("asyncFunction");
resolve();
}, 1000);
});
}

async function helloAsync() {
let promiseData = await asyncFunction();
console.log("helloAsync");
}
helloAsync();
// asyncFunction
// helloAsync

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function a() {
console.log("1");
console.log("2");
}
a();
console.log("3");
//打印: 1 2 3

async function a() {
await 1;
console.log("1");
console.log("2");
}
a();
console.log("3");
//打印: 3 1 2

参考文档

  1. https://www.runoob.com/w3cnote/es6-promise.html
  2. https://www.runoob.com/js/js-promise.html
  3. https://www.runoob.com/js/js-async.html
  4. https://www.runoob.com/w3cnote/es6-async.html
  5. https://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
  6. https://mp.weixin.qq.com/s/tGfC5XVuWXuSbG7wFLuaag

评论