await 等待的不是异步函数本身,而是 delay 函数执行后返回的 Promise 对象

一.先明确两个关键概念

  1. async 函数的特性:任何 async 函数执行后,必然返回一个 Promise 对象(哪怕你写 async function fn() { return 1 },也会被包装成 Promise.resolve(1));
  2. await 的作用:只能跟 Promise 对象(非 Promise 会被自动包装成 Promise.resolve(xxx)),并等待这个 Promise 从 pending 状态变为 fulfilled/rejected 后,才继续执行后续代码。

二.常见误区澄清

  1. 误区:await 等的是 delay 函数执行的 “耗时”❌ 错:delay 函数执行本身是 “瞬间完成” 的(只是创建了一个 Promise 并返回),耗时的是 Promise 内部的 setTimeout;✅ 对:await 等的是 Promise 从 pending 到 fulfilled 的状态变化,而这个变化由 setTimeout 触发。

  2. 误区:async 函数里必须有 await❌ 错:async 只是标记函数返回 Promise,哪怕没有 await,比如:

    async function fn() { return 1; }
    console.log(fn()); // 输出 Promise {<fulfilled>: 1}

三.await 核心功能之一就是「解包 Promise」 —— 它会等待 Promise 状态变为 fulfilled,并直接返回 Promise 内部 resolve 的值(相当于把 Promise 包裹的 “结果” 拆出来);如果 Promise 被 rejected,则会抛出错误(需用 try/catch 捕获)。

Promise 并不是 “必须用箭头函数作为参数”,而是 Promise 构造函数要求接收一个「执行器函数(executor)」作为参数 —— 这个执行器函数可以是箭头函数、普通函数声明、函数表达式等任意合法的函数形式,箭头函数只是最常用的写法(简洁、无 this 绑定问题)。

下面从「核心定义、参数本质、不同写法对比、执行器函数的作用」四个维度,把这个问题讲透:

一、Promise 构造函数的核心定义

先看 Promise 官方的构造函数签名:

new Promise(executor);
  • executor必须是一个函数(这是唯一要求),这个函数会被 Promise 立即执行,且接收两个内置参数:resolve(成功回调)和 reject(失败回调)。
  • 箭头函数只是 executor 的 “写法之一”,不是 “语法要求”。

二、Promise 参数的本质:执行器函数(executor)

Promise 构造函数的参数是一个「函数」,这个函数的核心特征:

  1. 必须接收 resolve 和 reject 两个参数(顺序固定,名称可自定义,但行业惯例用这两个名字);
  2. 被 Promise 立即同步执行(Promise 一创建,这个函数就会执行);
  3. 内部通常包裹异步操作(比如 setTimeout、接口请求),最终调用 resolve/reject 改变 Promise 状态。

三、Promise 参数的多种写法(箭头函数 vs 普通函数)

写法 1:箭头函数(最常用,推荐)

这是你代码中用的写法,优点是简洁、无 this 绑定(执行器函数中的 this 无实际意义,箭头函数更干净):

// 你的代码:箭头函数作为 executor
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功"); // 异步操作完成后调用 resolve
  }, 3000);
});
写法 2:普通函数表达式(合法,但冗余)
// 普通函数表达式作为 executor
const p = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve("成功");
  }, 3000);
});
写法 3:独立函数声明(拆分逻辑,适合复杂场景)

如果执行器内部逻辑复杂,可以把函数抽成独立声明:

// 独立声明执行器函数
function executor(resolve, reject) {
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve("成功");
    } else {
      reject("失败");
    }
  }, 3000);
}

// 传入 Promise 构造函数
const p = new Promise(executor); // 注意:只传函数名,不要加括号(加括号会立即执行)
写法 4:甚至可以用匿名函数(不推荐,可读性差)
const p = new Promise(function(res, rej) { // 参数名自定义(不推荐,不符合惯例)
  setTimeout(() => res("成功"), 3000);
});

四、关键细节:执行器函数的核心作用

不管用哪种函数写法,执行器函数的核心使命是:

  1. 启动异步操作(比如 setTimeout、fetch、文件读取);
  2. 在异步操作完成后,调用 resolve/reject 改变 Promise 状态
    • 调用 resolve(value):Promise 状态从 pending → fulfilled(成功),并把 value 作为结果保存;
    • 调用 reject(reason):Promise 状态从 pending → rejected(失败),并把 reason 作为错误保存;
  3. 同步执行特性:Promise 一创建,执行器函数就会立即执行(哪怕内部是异步操作):
    console.log("1. 开始创建 Promise");
    const p = new Promise((resolve) => {
      console.log("2. 执行器函数执行"); // 这行在创建 Promise 时立即打印
      setTimeout(() => {
        console.log("3. 异步操作完成"); // 3秒后打印
        resolve();
      }, 3000);
    });
    console.log("4. Promise 创建完成");
    // 打印顺序:1 → 2 → 4 → 3
    

五、为什么箭头函数是主流写法?

虽然 Promise 不强制用箭头函数,但箭头函数有两个核心优势,成为行业惯例:

  1. 语法简洁:少写 function 关键字,代码更紧凑(对比普通函数表达式,少了 function 和大括号的冗余);
  2. 无 this 绑定问题:执行器函数中的 this 本身没有实际作用(Promise 内部会忽略 this),箭头函数的 this 继承自外层作用域,不会出现普通函数中 this 指向全局 /window 的问题(避免不必要的 bug);
  3. 符合异步代码的书写习惯:现代 JS 中异步操作(setTimeout、fetch 回调)普遍用箭头函数,风格统一。

六、常见误区澄清

  1. 误区:Promise 参数必须是箭头函数❌ 错:只要是接收 resolve/reject 的函数即可,箭头函数只是写法之一;
  2. 误区:执行器函数可以不传 resolve/reject❌ 错:可以不传,但 Promise 会永远处于 pending 状态(无法完成),失去意义;
  3. 误区:执行器函数是异步的❌ 错:执行器函数本身是同步执行的,只是内部通常包裹异步操作。

async 函数并不会 “主动调用 resolve 方法”,但它的底层逻辑会自动将函数的返回值 / 异常映射为 Promise 的 resolve/reject 行为

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐