Promise

PromiseES6 引入的异步编程解决方案,用于解决传统回调函数嵌套 (回调地狱) 的问题,提供了更优雅、可维护的异步代码编写方式;

核心概念

  1. 什么是 PromisePromise 是一个对象,代表一个异步操作的最终完成 (或失败) 及其结果值,它有以下核心特征:

    1. 状态不可逆:Promise 一旦从 「未完成」 转为 「完成 / 失败」,状态永久固定,无法修改;
    2. 链式调用:通过 .then()/.catch()/.finally() 实现链式调用,替代回调嵌套;
    3. 异步执行:Promise 的回调函数会被放入微任务队列,而非同步执行;
  2. Promise 的三种状态:

    状态 描述 能否转换
    pending 初始状态,异步操作未完成 可转为 fulfilled/rejected
    fulfilled 异步操作成功 不可逆
    rejected 异步操作失败 不可逆
  3. 示例代码:

    let p1 = new Promise((resolve, reject) => {
      setTimeout(_ => {
        // 一旦状态被改变,再次执行 resolve、reject 就没有用了
        resolve('ok'); // pending → fulfilled
        reject('no');
      }, 200);
    });
    
    setTimeout(()=>{
      console.log('s', p1); // Promise { 'ok' }
    }, 1000);
    

基础用法

构造函数

  1. Promise 构造函数接收一个「执行器函数」 (executor),该函数立即执行,参数为两个回调:

    1. resolve(value):将 Promise 转为 fulfilled,并传递成功结果 value
    2. reject(reason):将 Promise 转为 rejected,并传递失败原因 reason
  2. 示例代码:

    // 封装异步操作(如 AJAX)
    function fetchData(url) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = () => {
          if (xhr.status === 200) {
            resolve(JSON.parse(xhr.responseText)); // 成功回调
          } else {
            reject(new Error(`请求失败:${xhr.status}`)); // 失败回调
          }
        };
        xhr.onerror = () => reject(new Error("网络错误"));
        xhr.send();
      });
    }
    
    // 使用
    fetchData("https://api.example.com/data")
      .then((data) => console.log("数据:", data))
      .catch((error) => console.error("错误:", error));
    

原型方法(链式调用核心)

  1. .then(onFulfilled, onRejected)

    1. 接收两个可选回调:
      1. onFulfilledPromise 成功时执行,参数为 resolve 传递的值;
      2. onRejectedPromise 失败时执行,参数为 reject 传递的原因;
    2. 返回值:
      1. 若回调返回普通值 (非 Promise),新 Promise 状态为 fulfilled,值为该返回值;
      2. 若回调返回 Promise,新 Promise 状态和值继承该 Promise
      3. 若回调抛出错误,新 Promise 状态为 rejected,值为抛出的错误;
    Promise.resolve(1)
      .then((v) => v + 1) // 返回 2 → 新 Promise 状态 fulfilled,值 2
      .then((v) => Promise.resolve(v * 2)) // 返回 Promise → 新 Promise 值 4
      .then((v) => { throw new Error("出错"); }) // 抛出错误 → 新 Promise rejected
      .catch((err) => console.log(err.message)); // 捕获错误:出错
    
  2. .catch(onRejected)

    1. 等价于 .then(null, onRejected),专门捕获 Promise 链中的错误;
    2. 错误会「冒泡」到最近的 .catch(),未捕获的错误会触发 unhandledrejection 事件;
    Promise.reject("失败")
      .then((v) => console.log(v)) // 跳过(Promise 已失败)
      .catch((err) => console.log(err)); // 捕获:失败
    
    // 捕获链式中的错误
    fetchData("https://api.example.com/data")
      .then((data) => {
        if (!data) {
          throw new Error("数据为空"); // 手动抛出错误
        }
        return data;
      })
      .catch((err) => console.log(err.message)); // 捕获:数据为空 / 请求失败
    
  3. .finally(onFinally)

    1. ES2018 新增,无论 Promise 成功 / 失败,都会执行该回调;
    2. 不接收参数 (无法判断状态),返回的 Promise 继承原状态和值 (除非 .finally() 抛出错误)
    3. 常用于「清理操作」 (如关闭加载动画、释放资源)
    console.log("开始请求");
    fetchData("https://api.example.com/data")
      .then((data) => console.log("数据:", data))
      .catch((err) => console.log("错误:", err))
      .finally(() => console.log("请求结束(无论成败)")); // 必执行
    

Promise 静态方法(工具方法)

  1. Promise.resolve(value)

    1. 快速创建一个 fulfilled 状态的 Promise
    2. valuePromise,则直接返回该 Promise;若 value.then() 方法 (thenable 对象),则转为 Promise 并执行 .then()
    // 普通值
    const p1 = Promise.resolve(1); 
    p1.then((v) => console.log(v)); // 1
    
    // Promise 对象
    const p2 = Promise.resolve(Promise.reject("失败"));
    p2.catch((err) => console.log(err)); // 失败
    
    // thenable 对象
    const thenable = {
      then(resolve) {
        resolve("thenable 转为 Promise");
      }
    };
    Promise.resolve(thenable).then((v) => console.log(v)); // thenable 转为 Promise
    
  2. Promise.reject(reason)

    1. 快速创建一个 rejected 状态的 Promise
    2. 注意:reason 不会被解析 (即使是 Promise,也直接作为失败原因)
    const p = Promise.reject(new Error("手动失败"));
    p.catch((err) => console.log(err.message)); // 手动失败
    
    // 区别于 Promise.resolve
    Promise.reject(Promise.resolve(1)).catch((err) => console.log(err)); // 直接打印 Promise 对象
    
  3. Promise.all(iterable)

    1. 接收一个可迭代对象 (如数组),包含多个 Promise
    2. 状态规则:
      1. 所有 Promisefulfilled → 新 Promise fulfilled,值为所有 Promise 结果的数组 (顺序与输入一致)
      2. 任意一个 Promise rejected → 新 Promise 立即 rejected,值为第一个失败的原因;
    3. 适用于「所有异步操作都完成后再执行」的场景 (如批量请求数据)
    const p1 = fetchData("https://api.example.com/user");
    const p2 = fetchData("https://api.example.com/posts");
    
    Promise.all([p1, p2])
      .then(([user, posts]) => { // 数组解构,顺序与输入一致
        console.log("用户:", user);
        console.log("帖子:", posts);
      })
      .catch((err) => console.log("失败原因:", err)); // 第一个失败的错误
    
  4. Promise.allSettled(iterable)

    1. ES2020 新增,等待所有 Promise 完成 (无论成败)
    2. Promise 始终 fulfilled,值为一个数组,每个元素包含对应 Promise 的状态和结果:
      1. 成功:{ status: "fulfilled", value: 结果 }
      2. 失败:{ status: "rejected", reason: 原因 }
    3. 适用于「需要知道所有异步操作结果(无论成败)」的场景 (如批量上传文件)
    const p1 = Promise.resolve("成功1");
    const p2 = Promise.reject("失败2");
    
    Promise.allSettled([p1, p2])
      .then((results) => {
        results.forEach((result, index) => {
          if (result.status === "fulfilled") {
            console.log(`第${index+1}个成功:`, result.value);
          } else {
            console.log(`第${index+1}个失败:`, result.reason);
          }
        });
      });
    // 输出:
    // 第1个成功:成功1
    // 第2个失败:失败2
    
  5. Promise.race(iterable)

    1. 「竞速」模式:第一个完成 (成功 / 失败)Promise 决定新 Promise 的状态和值;
    2. 适用于「超时控制」「最快响应优先」场景 (如同时请求多个 CDN,取最快的)
    // 超时控制:5秒内未完成则触发失败
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error("请求超时")), 5000);
    });
    
    Promise.race([fetchData("https://api.example.com/data"), timeoutPromise])
      .then((data) => console.log("数据:", data))
      .catch((err) => console.log(err.message)); // 要么数据返回,要么超时
    
  6. Promise.any(iterable)

    1. ES2021 新增,与 Promise.race 类似,但只关注「第一个成功」的 Promise
      1. 任意一个 Promise fulfilled → 新 Promise fulfilled,值为该成功结果;
      2. 所有 Promiserejected → 新 Promise rejected,值为 AggregateError (包含所有失败原因)
    2. 适用于「容错请求」 (如多个备用接口,取第一个成功的)
    const p1 = Promise.reject("接口1失败");
    const p2 = Promise.resolve("接口2成功");
    const p3 = Promise.reject("接口3失败");
    
    Promise.any([p1, p2, p3])
      .then((value) => console.log("成功:", value)) // 接口2成功
      .catch((err) => {
        console.log("所有接口失败:", err.errors); // [接口1失败, 接口3失败]
      });
    

关键特性与注意事项

  1. 状态不可逆:一旦 Promise 转为 fulfilled/rejected,后续调用 resolve/reject 无效:

    const p = new Promise((resolve, reject) => {
      resolve("成功");
      reject("失败"); // 无效(状态已固定)
    });
    p.then((v) => console.log(v)); // 成功
    
  2. 微任务执行时机:Promise 的回调 (.then()/.catch()/.finally()) 属于微任务,会在当前宏任务执行完、下一个宏任务执行前执行:

    console.log("同步代码1");
    Promise.resolve().then(() => console.log("Promise 微任务"));
    console.log("同步代码2");
    // 输出顺序:同步代码1 → 同步代码2 → Promise 微任务
    
  3. 错误捕获:

    1. .catch() 只能捕获其前面 Promise 链中的错误,无法捕获自身回调的错误;
    2. .then() 只能捕获其前面 Promise 链中的错误,无法捕获自身回调的错误;
    3. 未捕获的 Promise 错误会触发全局 unhandledrejection 事件:
    window.addEventListener("unhandledrejection", (event) => {
      event.preventDefault(); // 阻止控制台报错
      console.log("未捕获的 Promise 错误:", event.reason);
    });
    Promise.reject("未捕获的错误"); // 触发上述事件
    
  4. 避免回调地狱:Promise 链式调用替代嵌套回调;

    // 回调地狱(传统方式)
    fetchUser((user) => {
      fetchPosts(user.id, (posts) => {
        fetchComments(posts[0].id, (comments) => {
          console.log(comments);
        });
      });
    });
    
    // Promise 链式调用(优雅)
    fetchUser()
      .then((user) => fetchPosts(user.id))
      .then((posts) => fetchComments(posts[0].id))
      .then((comments) => console.log(comments))
      .catch((err) => console.log(err));
    

Generator

Generator (生成器)ES6 引入的一种特殊函数,核心特性是可暂停 / 恢复执行,并能逐步产生 (yield) 值,是实现迭代器、异步流程控制 (如替代回调 / Promise 链) 的重要工具;

核心概念

  1. 生成器函数的定义:生成器函数通过 function* 声明 (注意 * 位置:function* fn() / function *fn() / function * fn() 均可,推荐 function*),内部用 yield 关键字暂停执行并产出值;

    // 基础定义
    function* generatorFn() {
      yield 1;
      yield 2;
      return 3; // 结束时返回(迭代时不会被遍历到)
    }
    
  2. 生成器对象:调用生成器函数不会立即执行函数体,而是返回一个「生成器对象」 (同时实现 Iterator 和 Iterable 接口),这是控制生成器执行的唯一入口;

    const gen = generatorFn(); // 无执行,返回生成器对象
    console.log(gen); // Object [Generator] {}
    

核心方法与执行流程

  1. 生成器的执行完全由生成器对象的 next() 方法控制,核心流程是:next()执行到 yield暂停产出值

  2. 再次 next()从暂停处恢复执行

  1. next() 方法:

    1. 参数:可选,会作为上一个 yield 表达式的返回值;
    2. 返回值:一个对象,包含两个属性:
      1. value:当前 yield 产出的值 (或 return 的值)
      2. done:布尔值,false 表示未完成,true 表示生成器执行结束;
    function* genFn() {
      console.log('开始执行');
      const a = yield 1; // 第一个yield,产出1,暂停
      console.log('恢复执行,a =', a);
      const b = yield 2; // 第二个yield,产出2,暂停
      console.log('恢复执行,b =', b);
      return 3; // 结束
    }
    
    const gen = genFn(); // 无输出(函数未执行)
    
    // 第一次调用 next()
    console.log(gen.next()); 
    // 输出:开始执行 → { value: 1, done: false }
    
    // 第二次调用 next(10):参数10作为第一个yield的返回值(赋值给a)
    console.log(gen.next(10)); 
    // 输出:恢复执行,a = 10 → { value: 2, done: false }
    
    // 第三次调用 next(20):参数20作为第二个yield的返回值(赋值给b)
    console.log(gen.next(20)); 
    // 输出:恢复执行,b = 20 → { value: 3, done: true }
    
    // 第四次调用 next():生成器已结束
    console.log(gen.next()); 
    // { value: undefined, done: true }
    
  2. return() 方法:强制终止生成器,返回 { value: 传入的参数, done: true },后续调用 next() 始终返回 { value: undefined, done: true }

    const gen = genFn();
    console.log(gen.next());            // { value: 1, done: false }
    console.log(gen.return('强制结束'));// { value: '强制结束', done: true }
    console.log(gen.next());            // { value: undefined, done: true }
    
  3. throw() 方法:向生成器内部抛出错误,若未捕获则终止执行;若内部有 try/catch,则捕获错误后可继续执行;

    function* genFn() {
      try {
        yield 1;
      } catch (e) {
        console.log('捕获错误:', e);
        yield '错误后恢复';
      }
      yield 2;
    }
    
    const gen = genFn();
    console.log(gen.next());                        // { value: 1, done: false }
    console.log(gen.throw(new Error('测试错误')));  // 输出:捕获错误:Error: 测试错误 → { value: '错误后恢复', done: false }
    console.log(gen.next());                        // { value: 2, done: false }
    

yield* 委托迭代

  1. yield* 用于将迭代权委托给另一个可迭代对象 (生成器、数组、字符串等),会逐个产出委托对象的值;

  2. 委托给生成器

    function* gen1() {
      yield 1;
      yield 2;
    }
    
    function* gen2() {
      yield* gen1(); // 委托gen1
      yield 3;
    }
    
    const gen = gen2();
    console.log(gen.next()); // { value: 1, done: false }
    console.log(gen.next()); // { value: 2, done: false }
    console.log(gen.next()); // { value: 3, done: false }
    
  3. 委托给其他可迭代对象

    function* gen() {
      yield* [1, 2, 3]; // 数组
      yield* 'ab'; // 字符串
    }
    
    const genObj = gen();
    console.log([...genObj]); // [1, 2, 3, 'a', 'b']
    

生成器的迭代特性

  1. 生成器对象实现了 Iterable 接口 (有 [Symbol.iterator] 方法),因此可用于:

    1. for…of 循环 (自动遍历,直到 done: true,且忽略 return 的值)
    2. 扩展运算符
    3. Array.from() 等;
  2. 示例:for…of 遍历

    function* genFn() {
      yield 1;
      yield 2;
      return 3; // for...of 不会遍历此值
    }
    
    // for...of 自动调用 next(),直到 done: true
    for (const val of genFn()) {
      console.log(val); // 1 → 2
    }
    
    // 扩展运算符
    console.log([...genFn()]); // [1, 2]
    

生成器的典型应用

  1. 无限迭代器:生成器可轻松创建无限序列 (因惰性执行,不会立即占用内存)

    function* infiniteCounter() {
      let i = 0;
      while (true) {
        yield i++;
      }
    }
    
    const counter = infiniteCounter();
    console.log(counter.next().value); // 0
    console.log(counter.next().value); // 1
    console.log(counter.next().value); // 2
    // 按需取值,无内存溢出
    
  2. 惰性求值:按需生成数据,避免提前计算所有值 (如大数据集处理)

    // 生成指定范围的数(惰性生成,仅在next()时计算)
    function* range(start, end, step = 1) {
      for (let i = start; i <= end; i += step) {
        yield i;
      }
    }
    
    const nums = range(1, 1000000);
    console.log(nums.next().value); // 1(仅计算第一个值,无性能损耗)
    

async、await

async/awaitES2017 (ES8) 引入的异步编程语法糖,基于 Promise 实现,旨在解决 Promise 链式调用 (.then().catch()) 的可读性问题,让异步代码的写法更接近同步代码,是目前 JavaScript 异步编程的主流方案;

核心概念

  1. async 函数:

    1. 声明方式:在函数前加 async 关键字 (支持函数声明、函数表达式、箭头函数、方法等)
    2. 核心特性: async 函数的返回值必然是一个 Promise 对象 (无论函数内部返回什么值)
      1. 若函数内部返回非 Promise(如数字、字符串),会被包装为 resolved 状态的 Promise
      2. 若函数内部抛出错误,会被包装为 rejected 状态的 Promise
      3. 若函数内部返回一个 Promise,则直接返回该 Promise (状态由其决定)
    // 1. 返回非Promise值 → 包装为resolved的Promise
    async function fn1() {
      return 123;
    }
    console.log(fn1()); // Promise { 123 }
    fn1().then(res => console.log(res)); // 123
    
    // 2. 抛出错误 → 包装为rejected的Promise
    async function fn2() {
      throw new Error('测试错误');
    }
    fn2().catch(err => console.log(err)); // Error: 测试错误
    
    // 3. 返回Promise → 直接返回该Promise
    async function fn3() {
      return Promise.resolve('hello');
    }
    fn3().then(res => console.log(res)); // hello
    
  2. await 关键字:

    1. 作用:暂停 async 函数的执行,等待右侧 Promise 完成 (resolved/rejected) 后恢复执行,并返回 Promise 的解析值;若右侧不是 Promise,则直接返回该值;
    2. 使用限制:await 只能在 async 函数内部使用 (普通函数中使用会抛出语法错误)
    // 模拟异步请求
    function fetchData() {
      return new Promise(resolve => {
        setTimeout(() => resolve('异步数据'), 1000);
      });
    }
    
    async function getData() {
      console.log('开始请求');
      const data = await fetchData(); // 暂停执行,等待Promise resolved
      console.log('请求完成:', data); // 1秒后输出:请求完成:异步数据
      return data;
    }
    
    getData();
    

执行流程

  1. async/await 的执行流程核心是 「暂停 - 恢复」,本质是 Generator + Promise 的语法糖 (浏览器 / Node 底层自动实现了 Generator 的执行逻辑)

  2. 分步解析执行流程:

    // 模拟异步操作
    const delay = (time) => new Promise(resolve => {
      setTimeout(() => resolve(`延迟${time}ms`), time);
    });
    
    async function asyncFn() {
      console.log('步骤1:执行同步代码');
      const res1 = await delay(1000); // 暂停,等待Promise完成
      console.log('步骤2:', res1); // 1秒后恢复执行
      const res2 = await delay(500); // 再次暂停
      console.log('步骤3:', res2); // 再500ms后恢复执行
      return '最终结果';
    }
    
    // 调用async函数
    asyncFn().then(res => console.log('步骤4:', res));
    
    /* 输出顺序:
    步骤1:执行同步代码 → (等待1秒)
    步骤2:延迟1000ms → (等待500ms)
    步骤3:延迟500ms → 
    步骤4:最终结果
    */
    
  3. 关键流程总结:

    1. 调用 async 函数时,函数立即开始执行同步代码,直到遇到第一个 await
    2. 遇到 await 后,暂停 async 函数执行,将右侧的 Promise 放入微任务队列;
    3. 主线程继续执行其他代码,直到微任务队列执行;
    4. await 右侧的 Promise 状态变为 resolvedasync 函数恢复执行,await 返回 Promise 的解析值;若 Promise 变为 rejected,则抛出错误 (需捕获)
    5. 继续执行后续代码,直到下一个 await 或函数结束,最终返回一个 Promise

错误处理

  1. try/catch 捕获 (推荐):在 async 函数内部用 try/catch 包裹 await 语句,捕获单个 / 多个异步操作的错误:

    async function handleError() {
      try {
        // 模拟rejected的Promise
        const res = await Promise.reject('请求失败');
        console.log(res); // 不会执行
      } catch (err) {
        console.log('捕获错误:', err); // 捕获错误:请求失败
      }
    
      // 多个await的错误捕获(可分开/统一捕获)
      try {
        await delay(1000);
        await Promise.reject('第二个错误');
      } catch (err) {
        console.log('捕获多个错误:', err); // 捕获多个错误:第二个错误
      }
    }
    
    handleError();
    
  2. 链式 catch 捕获:利用 async 函数返回 Promise 的特性,在函数调用后用 .catch() 捕获所有未处理的错误:

    async function fn() {
      await Promise.reject('全局错误');
    }
    
    fn().catch(err => console.log('全局捕获:', err)); // 全局捕获:全局错误
    
  3. 混合捕获 (精准 + 兜底)

    async function mixCatch() {
      try {
        await Promise.reject('精准错误');
      } catch (err) {
        console.log('精准捕获:', err); // 处理已知错误
        throw new Error('重新抛出的错误'); // 可重新抛出,由外层catch处理
      }
    }
    
    mixCatch().catch(err => console.log('兜底捕获:', err)); // 兜底捕获:Error: 重新抛出的错误
    

await 的并行 / 串行执行

  1. 串行执行 (默认):多个 await 依次执行,前一个完成后才执行后一个,总耗时为各异步操作耗时之和:

    async function serial() {
      const res1 = await delay(1000); // 1秒
      const res2 = await delay(500);  // 再500ms
      // 总耗时:1500ms
      console.log(res1, res2);
    }
    
  2. 并行执行 (优化性能):若多个异步操作无依赖,可通过 Promise.all 实现并行执行,总耗时为最长的异步操作耗时:

    async function parallel() {
      // 先创建所有Promise(并行开始)
      const promise1 = delay(1000);
      const promise2 = delay(500);
    
      // 再await所有结果
      const res1 = await promise1;
      const res2 = await promise2;
    
      // 总耗时:1000ms(最长的那个)
      console.log(res1, res2);
    }
    
    // 更简洁的写法
    async function parallelAll() {
      const [res1, res2] = await Promise.all([delay(1000), delay(500)]);
      console.log(res1, res2);
    }
    
  3. 部分并行 + 部分串行:

    async function mix() {
      // 第一步:并行执行两个异步操作
      const [res1, res2] = await Promise.all([delay(1000), delay(500)]);
      // 第二步:基于第一步的结果,串行执行下一个异步操作
      const res3 = await delay(800, res1 + res2);
      console.log(res3); // 总耗时:1000 + 800 = 1800ms
    }
    

async/await 与 Generator 的关系

  1. async/awaitGenerator + Promise 的语法糖,以下是手动用 Generator 模拟 async/await 的核心逻辑,帮助理解底层实现;

  2. 模拟实现 (简化版 co 库)

    // 手动执行生成器(模拟 co 库的核心逻辑)
    function run(genFn) {
      const gen = genFn(); // 获取生成器对象
    
      // 递归执行next
      function next(res) {
        const { value, done } = gen.next(res);
        if (done) return Promise.resolve(value); // 生成器结束,返回Promise
    
        // 若value是Promise,解析后继续执行;否则直接传入下一个next
        return Promise.resolve(value).then(next);
      }
    
      return next();
    }
    
    // 模拟异步操作
    const delay = (time) => new Promise(resolve => {
      setTimeout(() => resolve(`延迟${time}ms`), time);
    });
    
    // 用Generator模拟async函数
    function* genFn() {
      const res1 = yield delay(1000);
      const res2 = yield delay(500);
      return res1 + res2;
    }
    
    // 执行(效果等同于async/await)
    run(genFn).then(res => console.log(res)); // 延迟1000ms延迟500ms
    
  3. 核心差异:

    特性 Generator + Promise async/await
    执行方式 需手动编写执行器 (如 co) 语言层面自动执行,无需手动
    错误处理 需手动捕获 throw () 原生支持 try/catch
    语法简洁性 繁琐 (需 yield/next) 简洁 (接近同步代码)
    语义化 (迭代器语义) (异步语义)

面试题

中断 Promise 的 .then 链

  1. 核心原理:.then 回调的执行依赖前一个 Promise 的状态:

    1. 若前一个 Promisepending 状态,后续 .then 会等待其状态变更,永远不会执行;
    2. 若前一个 Promiserejected 但后续无 .catch,错误会冒泡到全局,但 .then 仍会跳过;
    3. 若返回 rejected 且后续 .catch 捕获后不返回新值,链会继续 (需避免)
  2. 实现方式:

    // 工具函数:创建永久 pending 的 Promise
    const pausePromise = () => new Promise(() => {});
    
    // 中断 then 链示例
    Promise.resolve(1)
      .then((v) => {
        console.log("第一步:", v); // 执行
        if (v === 1) {
          return pausePromise(); // 返回 pending Promise,中断后续
        }
        return v + 1;
      })
      .then((v) => {
        console.log("第二步:", v); // 永不执行
      })
      .catch((err) => {
        console.log("捕获错误:", err); // 也不执行(无错误)
      });
    
    Promise.resolve(1)
      .then((v) => {
        console.log("第一步:", v);
        if (v === 1) {
          return Promise.reject("中断链"); // 返回 rejected
        }
        return v + 1;
      })
      .then((v) => {
        console.log("第二步:", v); // 跳过(前一个 Promise 失败)
      });
    // 注意:未捕获的 rejected 会触发 `unhandledrejection` 事件,需谨慎
    
    Promise.resolve(1)
      .then((v) => {
        throw new Error("触发中断");
      })
      .catch((err) => {
        console.log("捕获错误:", err.message); // 执行
        return pausePromise(); // 中断后续 then
      })
      .then((v) => {
        console.log("后续步骤:", v); // 永不执行
      });
    

中断 Promise 的本质(Promise 状态不可逆)

  1. Promise 的核心特性是状态不可逆:一旦转为 fulfilled/rejected,无法回退到 pending,也无法修改结果;因此,「中断 Promise」并非「取消已执行的异步操作」,而是:

    1. 阻止 Promise 状态变更 (如不调用 resolve/reject)
    2. 阻止 Promise 结果的后续处理 (即中断 .then 链)
    3. (进阶)取消异步操作本身 (如 AbortController 中断 axios 请求)
  2. 实现方式:

    const unresolvablePromise = new Promise((resolve, reject) => {
      // 不调用 resolve/reject,Promise 永久 pending
    });
    unresolvablePromise
      .then(() => console.log("成功")) // 不执行
      .catch(() => console.log("失败")); // 不执行
    
    const controller = new AbortController();
    const signal = controller.signal;
    
    // 封装带中断的请求
    const fetchWithAbort = (url) => {
      return new Promise((resolve, reject) => {
        fetch(url, { signal })
          .then((res) => res.json())
          .then(resolve)
          .catch((err) => {
            if (err.name === "AbortError") {
              console.log("请求已中断");
              return; // 不 reject,让 Promise 保持 pending
            }
            reject(err);
          });
      });
    };
    
    // 使用
    const requestPromise = fetchWithAbort("https://api.example.com/data");
    requestPromise.then((data) => console.log("数据:", data));
    
    // 500ms 后中断请求
    setTimeout(() => {
      controller.abort(); // 真实取消请求,触发 AbortError
    }, 500);
    
    /**
    * 封装可中断的 Axios 请求
    * @param {string} url - 请求地址
    * @param {AxiosRequestConfig} options - Axios 配置
    * @returns {Object} { promise: Promise, abort: Function }
    */
    function createAbortableAxios(url, options = {}) {
      const controller = new AbortController();
      const signal = controller.signal;
    
      // 封装 Axios Promise
      const axiosPromise = new Promise((resolve, reject) => {
        axios({
          url,
          ...options,
          signal, // 注入 signal
        })
          .then((response) => {
            resolve(response.data); // 直接返回响应数据
          })
          .catch((err) => {
            // 区分 Axios 中断错误
            if (axios.isCancel(err)) {
              reject(new Error("Axios 请求被手动取消"));
            } else if (err.name === "AbortError") {
              reject(new Error("Axios 请求被 AbortController 中断"));
            } else {
              reject(err); // 其他错误(网络/超时/业务错误)
            }
          });
      });
    
      return {
        promise: axiosPromise,
        abort: () => {
          controller.abort(); // 中断请求
          // 兼容旧版 CancelToken(可选)
          // const source = axios.CancelToken.source();
          // source.cancel("请求取消");
        },
      };
    }
    
    // 测试
    const { promise: axiosPromise, abort: abortAxios } = createAbortableAxios("https://api.example.com/user", { method: "POST", data: { id: 1 } });
    
    axiosPromise
      .then((data) => console.log("Axios 请求成功:", data))
      .catch((err) => console.log("Axios 错误:", err.message));
    
    // 点击按钮中断请求(真实业务场景)
    document.querySelector("#cancelBtn").addEventListener("click", () => {
      abortAxios();
    });
    

defer 的作用(延迟解析 Promise)

  1. defer 并非 ES6+ 原生 API,而是一种设计模式 (Deferred 模式),核心是将 Promiseresolve/reject 暴露到外部,实现 「延迟控制 Promise 状态」

  2. 原生 Promise 的痛点:原生 Promiseresolve/reject 只能在执行器内部调用,若需在外部触发 Promise 状态变更 (如事件回调、定时器外),则需 Deferred 模式;

    class Deferred {
      constructor() {
        // 创建 Promise,并将 resolve/reject 挂载到实例上
        this.promise = new Promise((resolve, reject) => {
          this.resolve = resolve;
          this.reject = reject;
        });
      }
    }
    
  3. 实现方式:

    // 等待按钮点击
    function waitButtonClick(btn) {
      const deferred = new Deferred();
    
      btn.addEventListener("click", (e) => {
        deferred.resolve(e); // 外部触发 resolve
      }, { once: true });
    
      return deferred.promise;
    }
    
    // 使用
    const btn = document.querySelector("#btn");
    waitButtonClick(btn).then((e) => {
      console.log("按钮点击:", e);
    });
    
    const deferred = new Deferred();
    
    // 多个依赖该 Promise 的回调
    deferred.promise.then(() => console.log("操作1完成"));
    deferred.promise.then(() => console.log("操作2完成"));
    
    // 手动控制时机(如用户点击确认后)
    document.querySelector("#confirm").addEventListener("click", () => {
      deferred.resolve(); // 一次性触发所有回调
    });
    
    function connectWebSocket(url) {
      const deferred = new Deferred();
      const ws = new WebSocket(url);
      ws.onopen = () => deferred.resolve(ws); // 连接成功解析
      ws.onerror = (err) => deferred.reject(err); // 失败拒绝
      return deferred.promise;
    }
    
    // 使用
    connectWebSocket("ws://example.com")
      .then((ws) => {
        ws.send("Hello");
      })
      .catch((err) => console.log("连接失败:", err));
    

封装一个延时微任务

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

async function test() {
    var temple = await sleep(1000);
    console.log(1111);
    return temple;
}
test();
function* sleep(ms) {
    yield new Promise((resolve, reject) => setTimeout(resolve, ms));
}

sleep(500).next().value.then(function () { 
  console.log(2222) 
});

看程序写结果(头条、字节)

  1. 题目

    async function async1() {
      console.log('async1 start');
    
      await async2();
      console.log('async1 end');
    
      // 浏览器环境:识别 await 后面跟的是 promise 的话默认就会直接调用 promise.then
      // 等价于 async2().then(() => console.log('async1 end'))
    
      // node 低版本环境:node 识别不出 await,则直接用 promsie 的 resolve 包裹起来,resole 里面是 promise 会调用 then,然后再调用 then(() => console.log('async1 end')),相当于调了两次 then,所以结果和浏览器的不太一致
      // new Promise((resolve, reject) => resolve(async2())).then(() => console.log('async1 end'));
    }
    
    async function async2() {
      console.log('async2');
    }
    
    console.log('script start');
    
    setTimeout(function () {
      console.log('setTimeout');
    }, 0)
    
    async1();
    
    new Promise(function (resolve) {
      console.log('promise1');
      resolve();
    }).then(function () {
      console.log('promise2');
    });
    
    console.log('script end');
    
    // 浏览器环境执行:
    //  script start
    //  async1 start
    //  async2
    //  promise1
    //  script end
    //  async1 end
    //  promise2
    //  setTimeout
    
  2. 图解

看程序写结果(字节)

  1. 题目

    console.log(1);
    
    setTimeout(_ => { console.log(2); }, 1000);
    
    async function fn() {
      console.log(3);
      setTimeout(_ => { console.log(4); }, 20);
      return Promise.reject();
    }
    
    async function run() {
      console.log(5);
      await fn();
      console.log(6);
    }
    
    run();
    
    // 需要执行 150MS 左右
    for (let i = 0; i < 90000000; i++) { }
    
    setTimeout(_ => {
      console.log(7);
      new Promise(resolve => {
        console.log(8);
        resolve();
      }).then(_ => { console.log(9); });
    }, 0);
    
    console.log(10);
    
    // 1
    // 5
    // 3
    // 10
    // 4
    // 7
    // 8
    // 9
    // 2
    
  2. 图解

看程序写结果

async function m1() {
  return 1;
}

async function m2() {
  const n = await m1();
  console.log(n);
  return 2;
}

async function m3() {
  const n = m2(); // 没有 await,返回值是 Promise<pending>
  console.log(n);
  return 3;
}

m3().then((n) => {
  console.log(n);
});

m3();

console.log(4);
// Promise { <pending> }
// Promise { <pending> }
// 4
// 1
// 3
// 1
var a;
var b = new Promise((resolve, reject) => {
  console.log('promise1');
  setTimeout(() => {
    resolve();
  }, 1000);
})
  .then(() => {
    console.log('promise2');
  })
  .then(() => {
    console.log('promise3');
  })
  .then(() => {
    console.log('promise4');
  });

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a;
  resolve(true);
  console.log('after2');
});

console.log('end');
// promise1
// undefined
// end
// promise2
// promise3
// promise4
// Promise { <pending> }
// after1
Promise.resolve(1).then((res) => {
    console.log(res)
    return 2
}).catch((err) => {
    return 3
}).then((res) => {
    console.log(res)
})

// 1
// 2
// 第一个 then 中没有异常,catch 捕获不到异常,则执行第二个 then
const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 1000)
})
const promise2 = promise1.then(() => {
    throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
    console.log('promise1~', promise1)
    console.log('promise2~', promise2)
}, 2000)

// promise1 Promise {<pending>}
// promise2 Promise {<pending>}
// promise1~ Promise {<fulfilled>: 'success'}
// promise2~ Promise {<rejected>: Error: error!!!}

// promise1、promise2 都是微任务执行,此时等待返回结果,状态为 pending
// 2s 后再次输出 promise1、promise2,此时微任务执行完毕,状态更新
setTimeout(() => console.log(5), 0);
new Promise(resolve => {
    console.log(1);
    resolve(3);
    Promise.resolve().then(() => console.log(4))
}).then(num => {
    console.log(num)
});
console.log(2);

// 1
// 2
// 4
// 3
// 5

// promise 构造函数里面是同步代码,构造函数的 resolve/reject 是异步的
// Promise.resolve().then 也是异步,此行代码先于 resolve 回调
Promise.resolve()
    .then(() => {
        return new Error('error!!!')
    })
    .then((res) => {
        console.log('then: ', res)
    })
    .catch((err) => {
        console.log('catch: ', err)
    })

// then:  Error: error!!!

// return 一个 error 对象并不会抛出错误,所以不会被后续的 catch 捕获
// 改成下面这样会被捕获异常
// return Promise.reject(new Error(‘error!!!’))
// throw new Error(‘error!!!’)
Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log)

// 1

// .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传
// 链式调用的参数不是函数,会发生值透传,传入的非函数值会被忽略
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));

first().then((arg) => {
    console.log(arg);
});

console.log(4);

// 3
// 7
// 4
// 1
// 2
// 5
var p = new Promise((resolve, reject) => {
    reject(Error('The Fails!'))
})
p.catch(error => console.log(error.message))
p.catch(error => console.log(error.message))

// The Fails!
// The Fails!
var p = new Promise((resolve, reject) => {
    // 创建并返回一个被拒绝(rejected)的 Promise 对象,在这个 Promise 对象被拒绝时,它将抛出一个包含错误信息的 Error 对象
    return Promise.reject(new Error('The Fails!'))
})
p.catch(error => console.log(error.message))
p.catch(error => console.log(error.message))

// Error: The Fails!
//     at /Users/wushuai/Desktop/javascript/1.js:2:27
//     at new Promise (<anonymous>)
//     at Object.<anonymous> (/Users/wushuai/Desktop/javascript/1.js:1:9)
//     at Module._compile (node:internal/modules/cjs/loader:1254:14)
//     at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
//     at Module.load (node:internal/modules/cjs/loader:1117:32)
//     at Module._load (node:internal/modules/cjs/loader:958:12)
//     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
//     at node:internal/main/run_main_module:23:47
async function async1() {
  console.log(1);
  const result = await async2();
  console.log(3);
}
async function async2() {
  console.log(2);
}
Promise.resolve().then(() => {
  console.log(4);
});
setTimeout(() => {
  console.log(5);
});
async1();
console.log(6);

// 1
// 2
// 6
// 4
// 3
// 5
Promise.resolve('Success!').then(() => {
    throw Error('Oh noes!')
}).catch(error => {
    console.log('actually, that worked');
    return 'actually, that worked'
}).then(data => {
    throw Error('The fails!')
}).catch(error => console.log(error.message))

// actually, that worked
// The fails!
new Promise((resolve, reject) => {
  resolve('Success!')
}).then(() => {
  throw Error('Oh noes!')
}).catch(error => {
  console.log('catch=>' + error)
  return "actually, that worked"
}).catch(error => console.log(error.message))

// catch=>Error: Oh noes!
var p = new Promise((resolve, reject) => {
  reject(Error('The Fails!'))
})
  .catch(error => console.log('catch=>' + error))
  .then(error => console.log('then=>' + error))

// catch=> Error: The Fails!
// then => undefined
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. Promise
    1. 1.1. 核心概念
    2. 1.2. 基础用法
      1. 1.2.1. 构造函数
      2. 1.2.2. 原型方法(链式调用核心)
      3. 1.2.3. Promise 静态方法(工具方法)
    3. 1.3. 关键特性与注意事项
  2. 2. Generator
    1. 2.1. 核心概念
    2. 2.2. 核心方法与执行流程
    3. 2.3. yield* 委托迭代
    4. 2.4. 生成器的迭代特性
    5. 2.5. 生成器的典型应用
  3. 3. async、await
    1. 3.1. 核心概念
    2. 3.2. 执行流程
    3. 3.3. 错误处理
    4. 3.4. await 的并行 / 串行执行
    5. 3.5. async/await 与 Generator 的关系
  4. 4. 面试题
    1. 4.1. 中断 Promise 的 .then 链
    2. 4.2. 中断 Promise 的本质(Promise 状态不可逆)
    3. 4.3. defer 的作用(延迟解析 Promise)
    4. 4.4. 封装一个延时微任务
    5. 4.5. 看程序写结果(头条、字节)
    6. 4.6. 看程序写结果(字节)
    7. 4.7. 看程序写结果