我正在参与「启航方案」

前言

在上篇文章一盏茶的时刻,聊聊 js 事情循环(Event loop)中,笔者大概叙述了一下 js 的履行机制-事情循环,此篇文章笔者迁就此论题,继续聊聊 js 的异步处理方案有哪些,以及各个方案的优缺陷是什么……

回调函数(callback)

回调函数 简略理解便是一个函数被作为参数传递给另一个函数。回调是前期最常用的一种异步处理方案。

回调并不一定便是异步,并没有直接关系。

举个简略的比如:

function f1(cb) {
  setTimeout(() => {
    cb && cb();
  }, 2000);
}
f1(() => {
  console.log("1");
});

如上,咱们运用 setTimeout 在函数 f1 中模仿了一个耗时 2s 的使命,耗时使命完毕后会抛出一个回调,那么咱们在调用时就能够做到在函数 f1 的耗时使命完毕后履行回调函数了。

选用这种方法,咱们把同步操作变成了异步操作,f1 不会堵塞程序运转,相当于先履行程序的主要逻辑,将耗时的操作推延履行。

回调优缺陷

长处:简略、容易理解

缺陷:代码不高雅,可读性差,不易保护,高度耦合,层层嵌套造成回调地狱

事情监听(发布订阅形式)

发布订阅形式 定义了目标间的一种一对多的依赖关系,当一个目标的状况发生改动时,所有依赖于它的目标都将会得到通知。

其实咱们都用过发布订阅形式,比如咱们在 DOM 节点上绑定一个事情函数:

document.body.addEventListener('click', function () {
  console.log('点击');
})

但这仅仅对发布订阅形式最简略的运用,在许多场景下咱们常常会运用一些自定义事情来满意咱们的需求。

发布订阅形式有许多种完成方法,下面咱们用 class 来简略完成下:

class Emitter {
  constructor() {
    // _listener数组,key为自定义事情名,value为履行回调数组-由于可能有多个
    this._listener = []
  }
  // 订阅 监听事情
  on(type, fn) {
    // 判别_listener数组中是否存在该事情命
    // 存在将回调push到事情名对应的value数组中,不存在直接新增
    this._listener[type] 
      ? this._listener[type].push(fn) 
    	: (this._listener[type] = [fn])
  }
  // 发布 触发事情
  trigger(type, ...rest) {
    // 判别该触发事情是否存在
    if (!this._listener[type]) return
    // 遍历履行该事情回调数组并传递参数
    this._listener[type].forEach(callback => callback(...rest))
  }
}

如上所示,咱们创立了一个 Emitter 类,并且添加了两个原型办法 ontrigger,运用如下:

// 创立一个emitter实例
const emitter = new Emitter()
emitter.on("done", function(arg1, arg2) {
  console.log(arg1, arg2)
})
emitter.on("done", function(arg1, arg2) {
  console.log(arg2, arg1)
})
function fn1() {
  console.log('我是主程序')
  setTimeout(() => {
    emitter.trigger("done", "异步参数一", "异步参数二")
  }, 1000)
}
fn1()

咱们先创立一个 emitter 实例,接着注册事情,再触发事情,也处理了异步问题。

事情监听优缺陷

长处:比较符合模块化思维,咱们自写监听器时能够做许多优化从而更好地监控程序运转。

缺陷:整个程序变成了事情驱动,流程上或多或少都会有点影响,每次运用还得注册事情监听再进行触发挺费事的,代码也不太高雅。

Promise

ES2015(ES6)规范化和引入了 Promise 目标,它是异步编程的一种处理方案。

简略来说便是用同步的方法写异步的代码,可用来处理回调地狱问题。

Promise 目标状况一旦改动,就不会再变,只要两种改变可能:

  1. Pending 变为 Resolved
  2. Pending 变为 Rejected

咱们用 setTimeout 模仿异步操作:

function analogAsync(n) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(n + 500), n);
  });
}
function fn1(n) {
  console.log(`step1 with ${n}`);
  return analogAsync(n);
}
function fn2(n) {
  console.log(`step2 with ${n}`);
  return analogAsync(n);
}
function fn3(n) {
  console.log(`step3 with ${n}`);
  return analogAsync(n);
}

Promise 来完成:

function fn() {
  let time1 = 0;
  fn1(time1)
    .then((time2) => fn2(time2))
    .then((time3) => fn3(time3))
    .then((res) => {
      console.log(`result is ${res}`);
    });
}
fn();

Promise 优缺陷

长处:Promise 用同步的方法写异步的代码,避免了层层嵌套的回调函数,可读性更好。链式操作,能够在 then 中继续写 Promise 目标并回来,然后继续调用 then 来进行回调操作。

缺陷:Promise 目标一旦新建就会立即履行,无法半途取消。若不设置回调函数,Promise 内部会抛出过错,不会流到外部。

Generator

Generator 其实是一个函数,只不过是一个特殊的函数。一般函数,你履行了这个函数,函数内部不会停,直到这个函数完毕。Generator 这个函数特殊之处便是中心能够停。

示例:

function *generatorFn() {
  console.log("a");
  yield '1';
  console.log("b");
  yield '2'; 
  console.log("c");
  return '3';
}
let it = generatorFn();
it.next();
it.next();
it.next();
it.next();

上面这个示例便是一个 Generator 函数,它有如下特点:

  • 不同于一般函数,Generator 函数在 function 后边,函数名之前有个 *
  • 函数内部有 yield 字段
  • 调用后其函数回来值运用了 next 办法

Generator 优缺陷

长处:高雅的流程操控方法,能够让函数可中止履行

缺陷:Generator 函数的履行必须靠履行器,只针对异步处理来说,仍是不太方便

async/await

ES2017 规范引入了async函数,使得异步操作变得更加方便。async是异步的意思,而awaitasync wait的简写,即异步等待,async/await的出现,被许多人认为是 js 异步操作的终究且最高雅的处理方案。

async 在做什么

async函数回来的是一个 Promise 目标,假如在async函数中直接 return 一个直接量,async会把这个直接量通过Promise.resolve()封装成 Promise 目标回来。

await 在等待什么

await等待的是一个表达式,这个表达式的核算成果是 Promise 目标或者其它值(换句话说,便是没有特殊限制,啥都行)。

  • 假如 await后边不是 Promise 目标,直接履行
  • 假如 await后边是 Promise 目标会堵塞后边的代码,Promise 目标resolve,然后得到resolve的值,作为await表达式的运算成果
  • await只能在async函数中运用

上述用 setTimeout 模仿异步操作,咱们用 async/await 来完成:

async function fn() {
  let time1 = 0;
  let time2 = await fn1(time1);
  let time3 = await fn2(time2);
  let res = await fn3(time3);
  console.log(`result is ${res}`);
}
fn();

输出成果和上面用 Promise 完成是相同的,但这个async/await代码结构看起来清晰得多,几乎跟同步写法相同,非常高雅。

async/await 优缺陷

长处:内置履行器,更好的语义,更广的适用性

缺陷:乱用await可能会导致性能问题,由于await会堵塞代码

参考:「硬核JS」深化了解异步处理方案

总结

以上便是笔者关于 js 异步处理方案的概述,以及对它们的优缺陷的分析,如有不足欢迎我们指出,假如我们觉得还不错的话,不要忘了点赞呦~