异步编程,是JavaScript编程中重要的一部分,最近学习了阮一峰老师的《深入掌握 ECMAScript 6 异步编程》系列文章,特意输出一篇学习笔记。
传统的四种异步编程方法
回调函数
JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
假设有三个函数f1
、f2
和f3
,f2
需要等待f1
的执行结果,这时最简单的方法就是把f2
写成f1
的回调函数:
js
function f1(callback){
setTimeout(()=>{
console.log(1);
callback();
},1000);
};
function f2(){
console.log(2);
}
function f3(){
console.log(3);
}
f1(this.f2);
f3();
回调函数是异步编程最基本的方法,通过这种方法,函数f1
不会阻塞程序的运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
事件监听
JavaScript 事件处理是异步的。在 JavaScript 中,事件监听器会被添加到事件队列,等待主执行线程的处理。当主线程空闲时,事件队列中的事件会按顺序被执行。因此,事件回调函数不会阻塞主线程,也不会导致页面假死。
事件驱动模式,这种方法的优点是比较容易理解,并且可以同时绑定多个事件,每个事件可以指定多个回调函数,有利于实现模块化。
发布/订阅
这种异步编程是通过发布/订阅模式来实现的,这种方法的性质与"事件监听"类似。
这是一种广泛应用于异步编程的模式,是回调函数的事件化,常常用来解耦业务逻辑。事件的发布者无需关注订阅的侦听器如何实现业务逻辑,甚至不用关注有多少个侦听器存在。数据通过消息的方式可以灵活的传递。 ------《深入浅出Nodejs》
Promises对象
Promises是一个对象,它为异步编程提供了一个统一的接口,关于Promise的详细解释,我曾经做过一个很详细的用法说明。
使用Promises对象实现异步编程时,每一个异步任务返回一个Promise对象,该对象有一个then方法,开发者可以在then方法中指定回调函数。
还是以函数f1
、f2
和f3
为例子,这是后的代码就应该是下面这个样子:
js
function f1(){
var a = new Promise((res, rej) => {
setTimeout(() => {
console.log(1);
res('1 done');
}, 1000);
});
return a;
};
function f2(){
console.log(2);
}
function f3(){
console.log(3);
}
f1().then(() => {
this.f2();
})
f3();
和前面几种方法相比,Promise的回调函数变成了链式写法,而且Promises对象有一整套的配套方法,可以实现很多强大的功能。
ECMAScript 6中的四种异步编程方法
Generator函数
Generator函数最大的特点就是可以暂停执行,它和普通函数的在写法上有两个区别:
- 在函数名之前加
星号
以示区别 - 异步操作需要暂停的地方,使用
yield语句
注明
js
function* gen(x){
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
使用星号标记以后,整个Generator函数就是一个封装的异步任务,yield语句就是异步操作要暂停的地方。
和普通函数不同的是,Generator函数执行之后返回的是一个指针对象,而不是return的值
上面的代码中,var g = gen(1);
调用Generator函数会返回一个内部指针g
。指针g
有一个next方法,会执行异步任务的第一阶段(既指针指向内部的第一个yield语句)。next方法会返回一个对象,表示当前阶段的信息(value属性和done属性),其中value属性指的是当前yield语句后面表达式的值,done属性是一个布尔值,表示当前Generator函数是否执行完毕。
Generator函数的数据交换和错误处理
用Generator函数来处理异步编程的原因,是因为它有暂停执行和恢复执行的能力,并且Generator函数有两个特性:函数体内外数据交换机制和错误处理机制。
内外数据交换
next方法的返回值是一个对象,对象中有一个value属性,这个value属性就是Generator函数向函数体外部输出的数据。
同时,next方法也可以接受一个参数,这个参数就是向Generator函数体内输入的数据,
js
function* gen(x){
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }
console.log(y) // 2
错误处理机制
js
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('出错了')// 出错了
async函数
async函数是一种Generator函数的语法糖。和前面的Generator函数相比,在写法上async函数就是将星号替换成了async,将yield替换成了await:
js
async function gen(x){
var y = await x + 2;
return y;
}