javascript 异步编程
js 是一种"单线程”(single thread)执行环境的语言,所以在任务执行的过程中是按照队列的形式,当一个任务执行完再执行下一个任务,这样的模式会因为一个任务执行时间较长的时候出现性能问题,例如页面假死状态。
为了解决这个问题,js将执行模式分为两种:同步(synchronous)和异步(asynchronous)
同步执行:后一个任务等待前一个任务执行完毕再执行。
异步执行:前一个任务可传入一个回调函数,在执行完毕后调用回调函数,后一个任务不等前一个任务执行完毕就会执行。
目前我总结的异步编程有一下几种方式:
- 回调函数
- 事件监听
- 发布/订阅
- promise
- async/awaits
1.回调函数
对调函数是最简单直接的方法,给执行的异步函数传入一个函数作为回调函数,当异步返回结果的时候执行回调函数
function cbFun() {
console.log("hello word!");
}
function asyncFun(callback) {
setTimeout(() => {
callback();
}, 3000);
}
asyncFun(cbFun);
2.事件监听
另一种思路采用事件驱动模式,任务的执行不取决于代码的顺序,而取决于事件的是否发生,这里用eventEmitter3 这个插件来实现
function cbFun() {
console.log("hello word!");
}
function asyncFun() {
setTimeout(() => {
emitter.emit("msg");
}, 3000);
}
var emitter = new EventEmitter3();
emitter.on("msg", cbFun);
// 执行异步函数
asyncFun()
这种方法有点比较容易理解,可以绑定多个事件,每个事件都可以指定多个回调函数,可以”去耦合”(decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动,运行流程会变的不清晰。
3.发布/订阅
我们可以把事件驱动看作成信号驱动,我们假定存在一个”信号中心“,某个任务完成就会去信号中心发布一个信号,其他任务可以在信号中心订阅这个信号,从而指导自己什么时候开始执行,这种模式叫做”发布/订阅模式“(publish-subscribe pattern).下面是一种实现发布订阅模式的对象
var PubSub = function() {
this.list = {};
};
//给订阅者提供订阅的方法
PubSub.prototype.subscribe = function(key, callback) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(callback);
};
//发布消息的功能
PubSub.prototype.publish = function(key, args) {
var fns = this.list[key];
// 如果没有订阅过该消息的话,则返回
if (!fns || fns.length === 0) {
return;
}
var len = fns.length;
while (len--) {
fns[len].apply(this, args);
}
};
// 取消订阅
PubSub.prototype.unsubscribe = function(key, fn) {
var fns = this.list[key];
if (!fns || fns.length === 0) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
// 将fn删除
var len = fns.length;
while (len--) {
if (fns[len] === fn) {
fns.splice(len, 1);
}
}
}
};
下面利用发布/订阅模式实现异步编程
var pubsub = new PubSub();
pubsub.subscribe("msg", cbFun);
function cbFun() {
console.log("hello word!");
}
function asyncFun() {
setTimeout(() => {
pubsub.publish("msg");
}, 3000);
}
asyncFun();
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
4.Promise
Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
function cbFun() {
console.log("hello word!");
}
function asyncFun(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 3000);
});
}
asyncFun().then(()=>{
cbFun()
})
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
5.async/await
目前async/await方法被作为前端异步的终极方案,因为其在语义化方面非常友好,对于代码的维护非常简单,只需要返回promise并await 它就好。
function cbFun() {
console.log("hello word!");
}
function asyncFun() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 3000);
});
}
async function asyncFunArr() {
await asyncFun();
cbFun();
}
asyncFunArr();
参考:
Javascript异步编程的4种方法