Skip to content
Go back

同步执行异步任务还能这样做?

Edit page

说到同步执行异步任务,最先想到的就是 Promiseasync/await 等常见方式 —— 它们通过语法层面的封装,使得异步代码看起来像是同步执行,极大地提升了代码的可读性和可维护性。但除了这些还有其它方式吗?当然有!

本文将介绍一种特殊的做法,借助异常机制将异步操作“同步化”,适用于一些控制流调度、SSR 框架内部等高级用法。

目标场景

首先定义一个异步请求函数

const globalData = {
  request: function () {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve("success");
      }, 1000);
    });
  },
};

以同步的方式调用它,拿到的是一个 Promise,并不能直接拿到结果。

function main() {
  console.log("main start");
  const result = globalData.request();
  console.log("result", result); // Promise {...}
}

但是最终的效果是期望直接拿到结果而不是 Promise,该如何实现呢?

核心实现

实现思路:用异常“中断”,用缓存“同步”

  1. 劫持异步函数,首次调用时直接 throw Promise 暂停当前函数的执行;
  2. 捕获该 Promise,等它完成后缓存结果,再重新执行函数。

React 的服务器端渲染(React Suspense + RSC)使用的就是这个机制。

第一步:劫持异步函数

改写 globalData.request 函数,使其在首次调用时抛出一个 Promise。等到 Promise 完成后,结果会被写入缓存。

function run(fun) {
  const oldRequest = globalData.request;
  const cache = {
    status: 'pending', // 'pending', 'fulfilled'
    value: null,
  };
  function newRequest(...orgs) {
    if (cache.status === 'fulfilled') {
      return cache.value;
    }
    const p = oldRequest(...orgs)
      .then((res) => {
        cache.status = 'fulfilled';
        cache.value = res;
      });
    throw p;
  }
  globalData.request = newRequest;
  ...
}

第二步:捕获 Promise,重试执行函数

拿到捕获的 Promise,当 Promise 完成时再次调用函数获取缓存中的结果。

function run(fun) {
  ...
  try {
    fun(); // 运行 main()
  } catch (err) {
    if (err instanceof Promise) {
      err.then(() => {
        globalData.request = newRequest; // 再次劫持
        fun(); // 重试执行
      }).catch((e) => {
        console.error("Error occurred in the promise:", e);
      }).finally(() => {
        globalData.request = oldRequest; // 恢复原始函数
      });
    }
  }
  globalData.request = oldRequest;
}

运行结果

run(main);
# 第一次执行输出
main start

# 第二次执行输出
main start
result success

总结

通过“抛出异常 + 缓存结果 + 重试”的方式,实现了异步任务的“同步化”,这背后的思路其实并不复杂:用异常中断当前流程,用缓存提供未来值。

虽然这种做法非常巧妙,但是并不适合日常业务代码,更适合在框架设计、底层架构中使用。不过可以当作一种思路储备,尤其是在面试中被问到时,能答出来绝对是一大亮点!


Edit page
Share this post on:

Next Post
CSS Anchor Position