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种方式的主要内容,如果未能解决你的问题,请参考以下文章

异步编程 | Js实现定时打印的几种方式

JavaScript 工作原理之四-事件循环及异步编程的出现和 5 种更好的 async/await 编程方式(译)

js异步加载的5种方式

如何在一个类中实现异步

八种实现异步编程的方式,你了解几个?

进阶学习5:JavaScript异步编程——同步模式异步模式调用栈工作线程消息队列事件循环回调函数