JavaScript 异步编程发展史

2023-05-22· 8min

#同步、异步的概念

  • 同步(Synchronous, sync)
    • 单线程编程中,代码是同步运行的(但同步不意味着所有代码同时运行,而是指在一个控制流序列中按顺序执行)
  • 异步(Asynchronous, async)
    • 不按照代码顺序执行

异步编程方案:异步回调、事件监听、定时器、发布/订阅模式、Promise、Generator、async/await...

#一、回调函数(Callback)

  • 优点:容易理解和实现、同时解决了因任务排队耗时久的同步执行问题
  • 缺点:会造成回调地狱(嵌套层级高)、可读性差
// 举例:多个操作之间如果有先后依赖关系的话,会出现回调地狱

doSomething((result) => {
  doSomething(result, (newResult) => {
    doSomething(newResult, (finalResult) => {});
  });
});

#二、Promise

  • Promise
    本质上是一个函数返回的对象,可以获取异步操作的最终状态(成功、失败)
  • 特点:
    • Promise
      的状态不受外界影响:pending、fulfilled、rejected
    • Promise
      的状态一旦改变,就不会再变(pending -> fulfilled、pending -> rejected)
  • 优点:用同步的方式写异步的代码、解决了回调地狱的问题、更好的异常错误处理
  • 缺点:
    • 一旦新建就会立即执行,无法中途取消
    • 错误需要通过回调函数捕获

#三、Generator

  • Generator
    函数是 ES6 中提供的一种异步编程解决方案。可以理解成:是一个状态机,封装了多个内部状态,需要使用 next() 函数来继续执行后续的代码
  • 特点:
    • function 与函数名之间带有
      *
      符号
    • 函数体内部使用 yield 表达式,函数执行遇到 yield 就返回
  • 优点:
    • 可以控制函数的执行(暂停执行、恢复执行)
  • 缺点
    • 需要手动控制
function* doSomething() {
  console.log("Start");
  yield 1;
  console.log("Middle");
  yield 2;
  console.log("End");
  return 3;
}

var genr = doSomething();

console.log(genr.next()); // Start、{ value: 1, done: false }
console.log(genr.next()); // Middle、{ value: 2, done: false }
console.log(genr.next()); // End、{ value: 3, done: true }
console.log(genr.next()); // { value: undefined, done: true }

#四、async、await

  • 实现原理:将 Generator 函数和自动执行器,包装在一个函数里。本质上是 Generator 的语法糖
  • 优点:
    • async 函数自带执行器(Generator 函数的执行则需要调用 next 方法)
    • 代码清晰,不用像 Promise 写一堆 then
    • 返回 Promise 对象(Generator 函数则返回迭代器对象)
  • 缺点
    • await 将异步代码改造成同步代码,如果多个异步操作间没有依赖,但使用 await 会导致性能降低
async function doSomething() {
  console.log("Start");
  await mockPause(1000);
  console.log("Middle");
  await mockPause(1000);
  console.log("End");
  return 3;
}

function mockPause(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

doSomething();
  • 拓展
    • 迭代器(Interator):
      • 是 ES6 引入的一种新的数据结构,是一种对象,提供了一种顺序访问集合中每个元素的方式。
      • 通过调用迭代器的 next() 方法,可以依次获取集合中的每个元素,并返回一个包含 value 和 done 属性的对象(value: 当前元素的值,done: 是否已经遍历完所有元素)
    • 迭代器与生成器的区别:
      • 迭代器(Interator)提供了一种顺序访问集合中每个元素的方式
      • 生成器(Generator)则允许函数在执行过程中暂停和恢复