JS异步编程的5种方式
Posted 总在落幕后
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS异步编程的5种方式相关的知识,希望对你有一定的参考价值。
前言
总所周知,JS语言的执行环境是单线程的(这里不考虑Worker),在单线程环境中,所有的任务都是串行执行的,一旦某个任务长时间运行,那么,后续的任务都将阻塞。为了解决这个问题,javascript语言将任务的执行模式分成两种:同步和异步。
本文将会介绍异步编程的5种方式:
回调函数callback
观察者模式 or 发布订阅模式
Promise
生成器generator和yield
async和await
最后,还会带大家手撸一份简单的Promise和async函数。阅读本文,小编建议大家先去简单的了解一下Promise,Generator,async。
正文
01 回调函数
回调函数是异步编程最基础的方式,因为在JS中函数是“一等公民”,所以回调函数可以作为参数传递到主体函数并执行。代码如下,
function call(callback) {
setTimeout(() => {
callback();
}, 300);
console.log('call');
}
function callee() {
console.log('callee');
}
call(callee);
执行代码的效果是 callee -> call,callee函数在300毫秒后执行,并且不会阻塞call函数内的其他代码。
回调函数的优点是简单、容易理解和部署。比如vue-rouer的导航守卫和nodeJS中的next都是使用回调函数的例子,其缺点是不利于代码的阅读和维护,各个部分高度耦合,多个异步操作下容易形成回调地狱。
02 观察者模式 or 发布订阅模式
观察者模式和发布订阅模式同样也可以用来异步编程,只需要在异步结果返回后,发布或触发对应的函数即可,相应的代码如下,
const eventBus = {
events: {},
on(e, f) {
this.events[e] ? this.events[e].push(f) : (this.events[e] = [f]);
},
emit(e) {
let event = this.events[e];
event && event.length && event.map((f) => f());
}
};
function fun() {
console.log('fun');
}
function eve() {
setTimeout(() => {
eventBus.emit('event');
}, 300);
console.log('eve');
}
eventBus.on('event', fun);
eve();
上面的代码也能达到不阻塞的效果,(两种模式可以查看小编之前写的文章),整个流程变成了事件驱动,就像我们DOM的事件处理一样。这种模式也比较容易理解,可以去耦合,形成模块化,但是代码的运行流程就不是很透明了。
03 Promise
ES6给我们带来了新的异步处理方案:Promise - Promise对象表示异步操作的最终完成(或失败)及其结果值。Promise有三种状态:初始态pending, 完成态fulfilled,使用then方法接收,失败态rejected,使用catch方法接收。下面请看例子,
new Promise((resolve,reject) => {
setTimeout(() => {
resolve();
}, 300);
console.log('p');
}).then(() => {
console.log('then');
});
Promise将异步处理变成了链式操作,解决了回调地域的问题,并且有一套完整的处理机制,使得流程更加清晰。但是Promise也有几个不能忽视的缺点,比如如果不设置回调函数,那么promise内部的错误就无法反映到外部,同时Promise的后续处理是一个微任务,将将响应回调函数延迟到同步代码的后面,一定程度上降低了效率。如果需要处理多个异步操作,那么会看到一堆的then,代码的可阅读性就降低了。
04 生成器Generator和yield
Generator函数和yield关键字是ES6提供的另一种改变执行流程,进行异步编程的方案。Generator函数有两个区分于普通函数的部分:一是在 function 后面,函数名之前有个 * ;二是函数内部有 yield 表达式。下面请看例子,
function run() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('success');
}, 300);
});
}
function* gen() {
let data = yield run();
console.log('data = ', data);
}
let it = gen();
it.next() // {value: Promise, done: false}
it.next() // {value: undefined, done: true}
generator函数会返回一个迭代器,通过next方法执行下一个流程,也就是每一个yield关键字的上一行代码。虽然 Generator函数也能用于处理异步,但是如果generator函数内有多个流程yield,那么,我们得一个一个的next下去,所以,generator函数更适合于做流程控制。
function* gen() {
let data = yield run();
console.log('data = ', data);
let data = yield run();
console.log('data = ', data);
let data = yield run();
console.log('data = ', data);
}
为了解决这个问题,async随着诞生。
05 async和await
ES6的Promise和Generator虽然提供了很好的异步处理方案,但在使用和理解上稍微有点复杂,所以在ES7带来了新的关键字async和await, async函数本质是Generator函数的语法 以上是关于JS异步编程的5种方式的主要内容,如果未能解决你的问题,请参考以下文章