You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用async/await进行顺序调用时返回过早的问题排查

为什么async/await没有按预期阻塞代码执行?

我原本希望async函数fetchAsync在返回数据(或抛出异常)前保持阻塞状态,但实际输出的顺序和我预期的不一样。下面是使用Fetch API的示例代码,我本来以为要等对象完成文件内容初始化后,*** we are here!!! ***才会在finish initialization之后打印,但现在的顺序完全相反,我有点疑惑async/await是不是应该阻塞到执行完成才继续?

实际输出:

constructor
entering fetchAsync...
*** we are here!!! ***
leaving fetchAsync.
finish initialization

示例代码:

class A {
  filename = "./resources/test.json";
  constructor() {
    console.log("constructor");
    this.fetchAsync(this.filename)
      .then(data => this.initialize(data)).catch(reason => console.log(reason.message))
  }
  async fetchAsync(filename) {
    console.log("entering fetchAsync...");
    let data = await (await fetch(filename)).json();
    console.log("leaving fetchAsync.");
    return data;
  }
  initialize() {
    setTimeout(() => console.log("finish initialization"), 1000)
  }
}
let a = new A();
console.log("*** we are here!!! ***");

别急,我来帮你理清楚这里的问题所在:

  • async函数本质是非阻塞的:哪怕你用了await,async函数一被调用就会立即返回一个Promise,不会阻塞后续同步代码的执行。所以当构造函数里调用this.fetchAsync()时,这个函数会启动异步请求,但构造函数不会等它完成,而是直接继续执行后面的代码——也就是console.log("*** we are here!!! ***"),这就是为什么这句话会提前打印。

  • 构造函数不能用async/await阻塞:JavaScript不允许构造函数被标记为async,因为构造函数必须返回实例本身,而不是Promise。所以你没法直接在构造函数里用await来等待异步操作完成。

  • initialize里的setTimeout也是异步的:就算fetchAsync完成后调用了initialize,里面的setTimeout是个宏任务,会把finish initialization的打印放到1秒后执行,所以它肯定会比前面的同步代码晚。

解决方法:让代码按预期顺序执行

如果你想让*** we are here!!! ***在finish initialization之后打印,可以试试这两种方案:

方案1:用静态async方法创建实例

通过静态方法来异步初始化实例,这样就能等待所有异步操作完成后再执行后续代码:

class A {
  filename = "./resources/test.json";
  constructor() {
    console.log("constructor");
  }
  async fetchAsync(filename) {
    console.log("entering fetchAsync...");
    let data = await (await fetch(filename)).json();
    console.log("leaving fetchAsync.");
    return data;
  }
  async initialize(data) {
    // 把setTimeout转为Promise,确保能被await等待
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log("finish initialization");
  }
  static async createInstance() {
    const a = new A();
    const data = await a.fetchAsync(a.filename);
    await a.initialize(data);
    return a;
  }
}

// 使用时等待实例初始化完成
A.createInstance().then(() => {
  console.log("*** we are here!!! ***");
}).catch(reason => console.log(reason.message));

方案2:暴露初始化Promise

在类里把初始化的Promise存起来,创建实例后手动等待这个Promise完成:

class A {
  filename = "./resources/test.json";
  initPromise; // 存储初始化的Promise
  constructor() {
    console.log("constructor");
    this.initPromise = this.fetchAsync(this.filename)
      .then(data => this.initialize(data)).catch(reason => console.log(reason.message))
  }
  async fetchAsync(filename) {
    console.log("entering fetchAsync...");
    let data = await (await fetch(filename)).json();
    console.log("leaving fetchAsync.");
    return data;
  }
  async initialize() {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log("finish initialization");
  }
}

// 创建实例后等待初始化完成
const a = new A();
a.initPromise.then(() => {
  console.log("*** we are here!!! ***");
});

核心总结:async/await只是让异步代码的写法更像同步,但它本质上还是异步的,不会阻塞整个事件循环。构造函数里的异步调用不会阻止后续同步代码执行,你需要显式等待这些异步操作完成后,再执行你想延后的代码。

内容的提问来源于stack exchange,提问作者John Difool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:59:16