JS异步任务发展历程

108 阅读4分钟

前置知识:事件循环与微任务机制

  • 宏任务(MacroTask)setTimeoutsetInterval,requestAnimationFrame,postMessage
  • 微任务(MicroTask)Promise回调:then、catch、finallyMutationObserver,queueMicrotask,async/await, await后的代码相当于微任务
特性宏任务(Macrotask)​微任务(Microtask)​
执行时机每次事件循环处理一个宏任务当前宏任务结束后立即清空所有微任务
队列处理单次处理一个任务一次性处理全部任务
优先级
常见 APIsetTimeout、DOM 事件、I/OPromiseMutationObserver

1. 回调函数时代

  • JavaScript早期的异步操作(如AJAX请求、定时任务)完全依赖回调函数实现。其本质是通过函数参数传递,将异步任务完成后的逻辑封装为函数,由事件循环触发执行。
fs.readFile('file1.txt', (err, data1) => {
  fs.readFile('file2.txt', (err, data2) => {
    fs.writeFile('result.txt', data1 + data2, (err) => {
      if (err) throw err;
      console.log('Done!');
    });
  });
});

缺点

  • 嵌套结构:回调函数支持链式调用,但多层嵌套会导致“回调地狱”(Callback Hell),代码可读性和可维护性极差。
  • 错误处理缺陷:错误需手动传递,缺乏统一机制,易导致未捕获异常
  • 典型应用setTimeoutXMLHttpRequest等API均采用回调模式。

2. Promise 的诞生(ES6 / ES2015)

  • ES6引入Promise,通过状态机模型(Pending/Fulfilled/Rejected)统一管理异步任务,解决回调地狱问题。
  • 通过链式调用 .then() 和 .catch() 扁平化异步流程。
readFile('file1.txt')
  .then(data1 => readFile('file2.txt'))
  .then(data2 => writeFile('result.txt', data1 + data2))
  .then(() => console.log('Done!'))
  .catch(err => console.error(err));

缺陷

  • 冗余语法:频繁使用.then()导致代码冗余,语义不够直观。
  • 无法取消:Promise一旦创建即执行,缺乏中断机制(需额外封装)。

3. 生成器

Generator 对象由生成器函数返回并且它符合可迭代协议迭代器协议

  • Generator 通过 yield 关键字暂停函数执行,并能在外部恢复,使得异步代码可以 以同步的方式编写
function* infinite() {
  let index = 0;

  while (true) {
    yield index++;
  }
}

const generator = infinite(); // "Generator { }"

console.log(generator.next().value); // 0
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
// generator+yeild实现同步执行异步任务
function* fetchUser() {
  const response = yield fetch('/api/user'); // 暂停,等待 fetch 完成
  const user = yield response.json();        // 再次暂停,等待解析 JSON
  return user;
}

// 执行器函数(如 co 库)
function run(generator) {
  const iterator = generator();
  function handle(result) {
    if (result.done) return result.value;
    return result.value.then(data => {
      return handle(iterator.next(data)); // 恢复执行并传递数据
    });
  }
  return handle(iterator.next());
}

run(fetchUser).then(user => console.log(user));

Generator 实例的 next()  方法返回一个包含属性 done 和 value 的对象。你也可以通过向 next 方法传入一个参数来向生成器传一个值。

function* gen() {
  while (true) {
    const value = yield;
    console.log(value);
  }
}

const g = gen();
g.next(1); // 返回 { value: undefined, done: false }
// 这一步不会有输出:通过 `next` 发送的第一个值会被丢弃
g.next(2); // 返回 { value: undefined, done: false }
// 打印 2

4. async/await 的成熟(ES2017)

异步函数的函数体可以被看作是由零个或者多个 await 表达式分割开来的。从顶层代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。因此,不包含 await 表达式的异步函数是同步运行的。然而,如果函数体内包含 await 表达式,则异步函数就一定会异步完成

async function processFiles() {
  try {
    const data1 = await readFile('file1.txt');
    const data2 = await readFile('file2.txt');
    await writeFile('result.txt', data1 + data2);
    console.log('Done!');
  } catch (err) {
    console.error(err);
  }
}
// 等价于
function processFiles(){
    return new Promise((resolve, reject)=>{
        readFile('file1.txt').then(data1=>{
            readFile('file2.txt').then(data2=>{
                writeFile('result.txt', data1 + data2).then(res=>{
                    resolve()
                })
            })
        })
    })
}

每个 await 表达式之后的代码可以被认为存在于 .then 回调中。通过这种方式,可以通过函数的每个可重入步骤来逐步构建 promise 链。而返回值构成了链中的最后一个环。

如果给定的值是一个 promise,异步函数会返回一个不同的引用,而 Promise.resolve 会返回相同的引用,

const p = new Promise((res, rej) => {
  res(1);
});

async function asyncReturn() {
  return p;
}

function basicReturn() {
  return Promise.resolve(p);
}

console.log(p === basicReturn()); // true
console.log(p === asyncReturn()); // false

promise 链不是一次就构建好的,相反,promise 、链是随着控制权依次在异步函数中交出并返回而分阶段构建的。

async/await捕获错误
function getProcessedData(url) {
  return downloadData(url) // 返回一个 promise
    .catch((e) => downloadFallbackData(url)) // 返回一个 promise
    .then((v) => processDataInWorker(v)); // 返回一个 promise
}

// try...catch
async function getProcessedData(url) {
  let v;
  try {
    v = await downloadData(url);
  } catch (e) {
    v = await downloadFallbackData(url);
  }
  return processDataInWorker(v);
}

async function getProcessedData(url) {
  const v = await downloadData(url).catch((e) => downloadFallbackData(url));
  return processDataInWorker(v);
}

未来趋势