Promise 基础技术深度解析:从回调地狱到链式调用

Ajax 进阶篇(ajax 封装) 之后,用 Promise 统一异步结果:三种状态、then/catch/finally 链式调用、ajaxPromise 与回调地狱扁平化。为 async/await 打底。

参考:MDN Promise | javascript.info Promise

目录

  • 零、导读与学习价值
    • [0.5 本章在学什么(知识地图)](#0.5 本章在学什么(知识地图))
    • [0.6 配套可运行示例(一分钟自检)](#0.6 配套可运行示例(一分钟自检))
    • [0.1 案例覆盖清单](#0.1 案例覆盖清单)
    • [0.2 核心名词速查](#0.2 核心名词速查)
    • [0.3 与 Ajax 基础篇 / Ajax 进阶篇 的衔接](#0.3 与 Ajax 基础篇 / Ajax 进阶篇 的衔接)
    • [0.4 建议练习路线](#0.4 建议练习路线)
  • [1. 异步编程概述](#1. 异步编程概述)
    • [1.1 同步与异步](#1.1 同步与异步)
    • [1.2 异步编程的场景](#1.2 异步编程的场景)
    • [1.3 回调函数的问题](#1.3 回调函数的问题)
    • [1.4 配套可运行示例(同步 vs 异步)](#1.4 配套可运行示例(同步 vs 异步))
    • [1.5 配套可运行示例(三层嵌套回调)](#1.5 配套可运行示例(三层嵌套回调))
  • [2. Promise 基本概念](#2. Promise 基本概念)
    • [2.1 Promise 是什么?](#2.1 Promise 是什么?)
    • [2.2 Promise 的特点](#2.2 Promise 的特点)
    • [2.3 Promise 的基本使用](#2.3 Promise 的基本使用)
    • [2.4 配套示例:创建 Promise 与 executor](#2.4 配套示例:创建 Promise 与 executor)
    • [2.5 配套可运行示例(状态不可逆)](#2.5 配套可运行示例(状态不可逆))
  • [3. Promise 状态详解](#3. Promise 状态详解)
    • [3.1 三种状态](#3.1 三种状态)
    • [3.2 状态转换示例](#3.2 状态转换示例)
    • [3.3 状态检查](#3.3 状态检查)
    • [3.4 配套可运行示例(状态不可逆)](#3.4 配套可运行示例(状态不可逆))
    • [3.5 配套可运行示例(pending → fulfilled)](#3.5 配套可运行示例(pending → fulfilled))
  • [4. Promise 基础语法](#4. Promise 基础语法)
    • [4.1 Promise 构造函数](#4.1 Promise 构造函数)
    • [4.2 resolve 和 reject](#4.2 resolve 和 reject)
    • [4.3 配套可运行示例(then 双回调)](#4.3 配套可运行示例(then 双回调))
  • [5. Promise 实例方法](#5. Promise 实例方法)
    • [5.1 then() 方法](#5.1 then() 方法)
    • [5.1.1 配套可运行示例(基础 then)](#5.1.1 配套可运行示例(基础 then))
    • [5.2 then() 返回值规则](#5.2 then() 返回值规则)
    • [5.3 catch() 方法](#5.3 catch() 方法)
    • [5.4 finally() 方法](#5.4 finally() 方法)
    • [5.5 配套可运行示例(then 返回值)](#5.5 配套可运行示例(then 返回值))
    • [5.6 配套可运行示例(catch + finally)](#5.6 配套可运行示例(catch + finally))
  • [6. Promise 链式调用](#6. Promise 链式调用)
    • [6.1 链式调用原理](#6.1 链式调用原理)
    • [6.4 配套示例:Ajax 回调地狱 → Promise 链](#6.4 配套示例:Ajax 回调地狱 → Promise 链)
    • [6.4.2 配套可运行示例(链尾 catch)](#6.4.2 配套可运行示例(链尾 catch))
    • [6.4.1 延伸练习:新歌榜前十首](#6.4.1 延伸练习:新歌榜前十首)
    • [6.5 配套示例:Node 顺序读文件](#6.5 配套示例:Node 顺序读文件)
    • [6.2 链式调用最佳实践](#6.2 链式调用最佳实践)
    • [6.3 错误传递机制](#6.3 错误传递机制)
    • [6.6 配套可运行示例(链式 mock)](#6.6 配套可运行示例(链式 mock))
  • [7. Promise 常见应用](#7. Promise 常见应用)
    • [7.1 网络请求封装](#7.1 网络请求封装)
    • [7.2 定时器封装](#7.2 定时器封装)
    • [7.3 文件操作封装](#7.3 文件操作封装)
    • [7.4 配套可运行示例(定时器 Promise)](#7.4 配套可运行示例(定时器 Promise))
    • [7.5 配套可运行示例(ajaxPromise 最小版)](#7.5 配套可运行示例(ajaxPromise 最小版))
  • [8. Promise 静态方法](#8. Promise 静态方法)
    • [8.1 Promise.resolve()](#8.1 Promise.resolve())
    • [8.2 Promise.reject()](#8.2 Promise.reject())
    • [8.2.1 配套可运行示例(仅 reject)](#8.2.1 配套可运行示例(仅 reject))
    • [8.3 配套可运行示例(Promise.resolve / reject)](#8.3 配套可运行示例(Promise.resolve / reject))
  • [9. 调试与错误处理](#9. 调试与错误处理)
    • [9.1 错误处理最佳实践](#9.1 错误处理最佳实践)
    • [9.2 调试技巧](#9.2 调试技巧)
    • [9.3 配套可运行示例(链式调试)](#9.3 配套可运行示例(链式调试))
    • 高频面试题速查
  • 总结

零、导读与学习价值

0.5 本章在学什么(知识地图)

#mermaid-svg-E9bwpOZqVZgnMS9f{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-E9bwpOZqVZgnMS9f .error-icon{fill:#552222;}#mermaid-svg-E9bwpOZqVZgnMS9f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-E9bwpOZqVZgnMS9f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-E9bwpOZqVZgnMS9f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-E9bwpOZqVZgnMS9f .marker.cross{stroke:#333333;}#mermaid-svg-E9bwpOZqVZgnMS9f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-E9bwpOZqVZgnMS9f p{margin:0;}#mermaid-svg-E9bwpOZqVZgnMS9f .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f .cluster-label text{fill:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f .cluster-label span{color:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f .cluster-label span p{background-color:transparent;}#mermaid-svg-E9bwpOZqVZgnMS9f .label text,#mermaid-svg-E9bwpOZqVZgnMS9f span{fill:#333;color:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f .node rect,#mermaid-svg-E9bwpOZqVZgnMS9f .node circle,#mermaid-svg-E9bwpOZqVZgnMS9f .node ellipse,#mermaid-svg-E9bwpOZqVZgnMS9f .node polygon,#mermaid-svg-E9bwpOZqVZgnMS9f .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-E9bwpOZqVZgnMS9f .rough-node .label text,#mermaid-svg-E9bwpOZqVZgnMS9f .node .label text,#mermaid-svg-E9bwpOZqVZgnMS9f .image-shape .label,#mermaid-svg-E9bwpOZqVZgnMS9f .icon-shape .label{text-anchor:middle;}#mermaid-svg-E9bwpOZqVZgnMS9f .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-E9bwpOZqVZgnMS9f .rough-node .label,#mermaid-svg-E9bwpOZqVZgnMS9f .node .label,#mermaid-svg-E9bwpOZqVZgnMS9f .image-shape .label,#mermaid-svg-E9bwpOZqVZgnMS9f .icon-shape .label{text-align:center;}#mermaid-svg-E9bwpOZqVZgnMS9f .node.clickable{cursor:pointer;}#mermaid-svg-E9bwpOZqVZgnMS9f .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-E9bwpOZqVZgnMS9f .arrowheadPath{fill:#333333;}#mermaid-svg-E9bwpOZqVZgnMS9f .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-E9bwpOZqVZgnMS9f .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-E9bwpOZqVZgnMS9f .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E9bwpOZqVZgnMS9f .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-E9bwpOZqVZgnMS9f .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E9bwpOZqVZgnMS9f .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-E9bwpOZqVZgnMS9f .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-E9bwpOZqVZgnMS9f .cluster text{fill:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f .cluster span{color:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-E9bwpOZqVZgnMS9f .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-E9bwpOZqVZgnMS9f rect.text{fill:none;stroke-width:0;}#mermaid-svg-E9bwpOZqVZgnMS9f .icon-shape,#mermaid-svg-E9bwpOZqVZgnMS9f .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E9bwpOZqVZgnMS9f .icon-shape p,#mermaid-svg-E9bwpOZqVZgnMS9f .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-E9bwpOZqVZgnMS9f .icon-shape .label rect,#mermaid-svg-E9bwpOZqVZgnMS9f .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E9bwpOZqVZgnMS9f .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-E9bwpOZqVZgnMS9f .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-E9bwpOZqVZgnMS9f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 异步痛点
Promise 三态
then/catch/finally
链式 ajax / fs
resolve/reject 静态方法
async/await 预告

【代码注释】

  • 左到右对应 01~05 配套示例练习顺序(§0.4)。
  • 链式调用是本章核心产出:消灭回调嵌套。

0.6 配套可运行示例(一分钟自检)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Promise 自检</title></head>
<body>
<script>
  Promise.resolve('ok').then(v => console.log('本章环境正常', v));
</script>
</body>
</html>

【代码注释】

  • 浏览器打开即打印则说明可运行 HTML 示例环境正常;再按 §0.4 打开 01~05 目录页面。

0.1 案例覆盖清单

目录 / 文件 知识点 本文章节
01-Promise对象/01-创建promise对象.html executor 同步执行、resolve/reject 为函数 §2.4、§4
01/02-改变promise对象的状态.html 状态不可逆、PromiseResult §3
01/03-为promise对象设置回调函数.html then 双回调、异步执行 §5.1
01/04-封装定时器Promise函数.html setTimeoutPromise §7.2
01/05-封装ajax promise 函数.html + ajax-promise.js XHR 返回 Promise §7.1
01/ajax.js Ajax 进阶篇 回调版 ajax,供对比 §6.4、§7.1
02-Promise的实例的方法/01-then方法.html then 返回值四种情况 §5.2
02/02-catch和finally.html then+catch+finally §5.3~5.4
03-Ajax回调地狱/ (含 ajax.jsajax-promise.js 与 01 目录同款封装 §6.4
03/01-回调地狱写法.html 三层嵌套 ajax({ success }) §6.4
03/02-then链式调用解决回调地狱.html return ajaxPromise 扁平链 §6.4
03/03-then和catch一起使用解决回调地狱.html 链尾单一 catch §6.4
04/01-按照顺序读取文件-回调地狱写法.js 嵌套 fs.readFile §6.5
04/02-按照顺序读取文件promise链式调用.js node:fs/promises §6.5
04/data1.txt~data3.txt Node 读文件练习数据 §6.5
05/01-resolve方法.html02-reject方法.html 静态方法四种参数 / reject §8
作业.md 新歌榜 3779629 前十首歌名+歌手 §6.4.1
网易云音乐API.md 接口基址与文档链接(延伸阅读) §6.4

0.2 核心名词速查

术语 一句话解释
executor new Promise((resolve, reject) => {}) 里同步执行的函数
PromiseResult resolve/reject 传入的值,then/catch 形参收到
fulfilled / resolved 成功态(课程部分资料写 resolved)
rejected 失败态
then 注册成功/失败回调,返回新 Promise
catch 失败回调语法糖,支持异常穿透
finally 无论成败都执行,常用于收尾
链式调用 return 下一异步,消灭回调嵌套

0.3 与 Ajax 基础篇 / Ajax 进阶篇 的衔接

  • Ajax 基础篇 :XHR 五步、onload / onerror 回调。
  • Ajax 进阶篇ajax({ success, error }) 回调版;记账本多次请求。
  • 本篇 :XHR 改为 ajaxPromise() 返回 Promise ,用 .then().catch() 串联多接口(03-Ajax回调地狱 示例)。
  • async/await 进阶(预告):语法糖建立在 then 返回值规则之上。

0.4 建议练习路线

顺序 目录 方式 验证点
01-Promise对象 浏览器打开 0105 的 HTML executor、状态、then 双参
02-Promise的实例的方法 打开 01-then02-catch和finally then 返回值、catch 穿透、finally
03-Ajax回调地狱 打开 HTML,需外网 API 嵌套 ajax → return ajaxPromise
04-Node回调地狱 node 运行两个 .js 回调嵌套 vs fs.promises
05-Promise类本身的方法 打开 resolve / reject HTML 四种参数情形
延伸练习 在 03 目录新建页面或控制台 新歌榜前十首,见 §6.4.1

Ajax 案例注意: HTML 通过 <script src="./ajax-promise.js"> 引入封装;基址 http://api.fuming.site:54255(与 作业.md网易云音乐API.md 一致)。

Node 案例注意:04-Node回调地狱 下执行:

bash 复制代码
node 01-按照顺序读取文件-回调地狱写法.js
node 02-按照顺序读取文件promise链式调用.js

【代码注释】

  • 04-Node回调地狱 目录执行;对比回调版与 Promise 链版控制台输出。
  • 需本机安装 Node.js;data1.txtdata3.txt 与脚本同目录。

1. 异步编程概述

1.1 同步与异步

同步操作:按照代码顺序执行,前一个操作完成后才会执行下一个操作。

javascript 复制代码
// 同步代码示例
console.log('1. 开始');
console.log('2. 执行中');
console.log('3. 结束');

// 执行顺序:1 -> 2 -> 3

【代码注释】

  • 同步代码按书写顺序执行;阻塞主线程直到当前脚本段结束。

异步操作:不阻塞代码执行,可以在等待某些操作完成时继续执行其他代码。

javascript 复制代码
// 异步代码示例
console.log('1. 开始');

setTimeout(() => {
    console.log('2. 定时器回调');
}, 1000);

console.log('3. 结束');

// 执行顺序:1 -> 3 -> 2(定时器回调在主线程空闲时执行)

【代码注释】

  • 宏任务:整段同步先打完 1、3,再进任务队列执行定时器打印 2。
  • 理解事件循环是读懂 then 微任务顺序的前提(async/await 进阶 会展开)。

1.2 异步编程的场景

常见异步操作:

javascript 复制代码
const asyncOperations = {
    // 1. 网络请求
    networkRequest: 'fetch(), XMLHttpRequest, axios等',
    
    // 2. 文件操作
    fileOperations: 'Node.js中的fs.readFile()等',
    
    // 3. 定时器
    timers: 'setTimeout(), setInterval()等',
    
    // 4. 事件处理
    eventHandling: '用户点击、键盘输入等事件',
    
    // 5. 动画效果
    animations: 'CSS动画、JavaScript动画等'
};

console.log('常见异步操作:', asyncOperations);

【代码注释】

  • 回调地狱:嵌套深、错误难统一处理;Promise 链式是主要替代方案。
  • Ajax 三接口串联见 03-01-回调地狱 vs 03-02-then链式

1.3 回调函数的问题

回调地狱示例:

javascript 复制代码
// 多层嵌套的回调函数(回调地狱)
getData(function(a) {
    getMoreData(a, function(b) {
        getMoreData(b, function(c) {
            getMoreData(c, function(d) {
                // 继续嵌套...
            });
        });
    });
});

// 问题:
// 1. 代码嵌套过深,难以阅读
// 2. 错误处理困难
// 3. 代码复用困难
// 4. 调试困难

【代码注释】

  • 回调地狱:嵌套深、错误难统一处理;Promise 链式是主要替代方案。
  • Ajax 三接口串联见 03-01-回调地狱 vs 03-02-then链式

【实战要点】

  • 先分清「同步代码」「宏任务(setTimeout)」「微任务(then)」的执行顺序,再写链式调用。
  • 回调版 ajax 能跑通后,再改 ajaxPromise,便于对比错误处理方式。

1.4 配套可运行示例(同步 vs 异步)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>同步异步对比</title></head>
<body>
<script>
  console.log('A');
  setTimeout(() => console.log('B 宏任务'), 0);
  Promise.resolve().then(() => console.log('C 微任务'));
  console.log('D');
</script>
</body>
</html>

【代码注释】

  • 典型输出 A → D → C → B:理解微任务先于宏任务,为 then 顺序打基础。

【面试考点】

Q1:什么是回调地狱?Promise 链如何解决?

A:多层嵌套回调难读难维护;每步 return Promise 扁平化。

Q2:同步与异步在 Event Loop 中的顺序?

A:同步先执行;微任务(then)先于宏任务(setTimeout)。

【本章小结】

概念 要点
同步 按行阻塞执行
异步 定时器、XHR、事件不阻塞后续代码
回调地狱 嵌套深、错误分散

1.5 配套可运行示例(三层嵌套回调)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>嵌套回调</title></head>
<body>
<script>
  const api = (id, cb) => setTimeout(() => cb(null, id + '-ok'), 50);
  api('a', (_, a) => api('b', (_, b) => api('c', (_, c) => console.log(a, b, c))));
</script>
</body>
</html>

【代码注释】

  • 模拟 03-01-回调地狱写法.html ;§6 用 return ajaxPromise 改写为链式。

2. Promise 基本概念

2.1 Promise 是什么?

Promise 是 JavaScript 中处理异步操作的对象,代表一个异步操作的最终完成或失败。

名词解析:

  • Promise:承诺,表示一个异步操作的最终结果
  • Pending:进行中,初始状态
  • Fulfilled:已成功,操作成功完成
  • Rejected:已失败,操作失败
  • Resolve:解决,将Promise状态改为成功
  • Reject:拒绝,将Promise状态改为失败

2.2 Promise 的特点

Promise 的三大特点:

javascript 复制代码
// 1. 对象的状态不受外界影响
const promise = new Promise((resolve, reject) => {
    // 只有异步操作的结果可以决定当前状态
    // 外界无法改变状态
});

// 2. 状态一旦改变就不会再变
const promise1 = new Promise((resolve, reject) => {
    resolve('成功');
    reject('失败'); // 无效,状态已经改变
});

// 3. 一旦状态改变就会触发相应的回调
const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('数据');
        // 状态变为 fulfilled,自动触发 then() 回调
    }, 1000);
});

【代码注释】

  • 结合本节标题在控制台逐步执行;注意 executor 同步、then 异步。
  • 状态一旦改变不可再变;错误应沿链用 catch 统一处理。

2.3 Promise 的基本使用

创建 Promise 对象:

javascript 复制代码
// 基础语法
const promise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
        const success = true;
        
        if (success) {
            // 操作成功,调用 resolve
            resolve('操作成功的结果');
        } else {
            // 操作失败,调用 reject
            reject(new Error('操作失败的原因'));
        }
    }, 1000);
});

// 使用 Promise
promise
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        console.error('失败:', error);
    });

【代码注释】

  • then/catch 在状态落定后作为微任务执行,不阻塞后续同步代码。
  • 对应 01/03-为promise对象设置回调函数.html

2.4 配套示例:创建 Promise 与 executor

01-创建promise对象.html 一致的三点结论:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>创建 Promise</title></head>
<body>
<script>
  const p1 = new Promise(() => {});
  console.log('空 executor,状态 pending:', p1);
  const p2 = new Promise(() => { console.log('executor 同步执行'); });
  console.log('p2 创建后立刻打印');
  const p3 = new Promise((resolve, reject) => {
    console.log('resolve、reject 类型:', typeof resolve, typeof reject);
  });
</script>
</body>
</html>

【代码注释】

  • new Promise(executor)executor 立即同步执行
  • resolve/reject 各只能有效调用一次;重复调用被忽略。
  • 控制台查看 Promise 对象初始为 pending

【实战要点】

  • 耗时异步应写在 executor 内,在回调里调用 resolve/reject。
  • executor 内 throw 等价 reject(err)

【面试考点】

Q1:Promise 构造函数参数何时执行?

A:创建时 executor 同步执行

Q2:resolve 多次调用会怎样?

A:仅第一次有效,状态不可逆。

2.5 配套可运行示例(状态不可逆)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>resolve 一次</title></head>
<body>
<script>
  const p = new Promise((resolve, reject) => {
    resolve('先成功');
    reject('后失败'); // 无效
  });
  p.then(v => console.log(v)).catch(e => console.log(e));
</script>
</body>
</html>

【代码注释】

  • 对应 02-改变promise对象的状态.html 核心结论。

【本章小结】

要点 说明
executor 创建时同步执行
三特点 外界不可改态、只变一次、触发回调

3. Promise 状态详解

3.1 三种状态

#mermaid-svg-9wQQzzZ4sJKwPYpv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9wQQzzZ4sJKwPYpv .error-icon{fill:#552222;}#mermaid-svg-9wQQzzZ4sJKwPYpv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9wQQzzZ4sJKwPYpv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9wQQzzZ4sJKwPYpv .marker.cross{stroke:#333333;}#mermaid-svg-9wQQzzZ4sJKwPYpv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9wQQzzZ4sJKwPYpv p{margin:0;}#mermaid-svg-9wQQzzZ4sJKwPYpv defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-9wQQzzZ4sJKwPYpv g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-9wQQzzZ4sJKwPYpv g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-9wQQzzZ4sJKwPYpv g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-9wQQzzZ4sJKwPYpv g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-9wQQzzZ4sJKwPYpv g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-9wQQzzZ4sJKwPYpv .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-9wQQzzZ4sJKwPYpv .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9wQQzzZ4sJKwPYpv .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9wQQzzZ4sJKwPYpv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9wQQzzZ4sJKwPYpv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9wQQzzZ4sJKwPYpv .edgeLabel .label text{fill:#333;}#mermaid-svg-9wQQzzZ4sJKwPYpv .label div .edgeLabel{color:#333;}#mermaid-svg-9wQQzzZ4sJKwPYpv .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-9wQQzzZ4sJKwPYpv .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-9wQQzzZ4sJKwPYpv .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-9wQQzzZ4sJKwPYpv .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-9wQQzzZ4sJKwPYpv .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-9wQQzzZ4sJKwPYpv .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9wQQzzZ4sJKwPYpv #statediagram-barbEnd{fill:#333333;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .cluster-label,#mermaid-svg-9wQQzzZ4sJKwPYpv .nodeLabel{color:#131300;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-9wQQzzZ4sJKwPYpv .note-edge{stroke-dasharray:5;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-note text{fill:black;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram-note .nodeLabel{color:black;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagram .edgeLabel{color:red;}#mermaid-svg-9wQQzzZ4sJKwPYpv #dependencyStart,#mermaid-svg-9wQQzzZ4sJKwPYpv #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-9wQQzzZ4sJKwPYpv .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9wQQzzZ4sJKwPYpv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} new Promise()
resolve(value)
reject(reason) / throw
pending
fulfilled
rejected
状态一旦改变不可逆

【代码注释】

  • pending :进行中;fulfilled (课程部分资料写 resolved):成功;rejected:失败。
  • 仅异步结果应调用 resolve/reject;外界不能直接改状态。
  • 对应 02-改变promise对象的状态.html 中先 rejectresolve 仍保持 rejected。

Promise 状态机(代码对照):

javascript 复制代码
const PROMISE_STATES = {
    PENDING: 'pending',           // 进行中,初始状态
    FULFILLED: 'fulfilled',       // 已成功
    REJECTED: 'rejected'          // 已失败
};

// 状态转换规则:
// pending -> fulfilled (成功)
// pending -> rejected (失败)
// 状态一旦改变就不可逆

console.log('Promise 状态:', PROMISE_STATES);

【代码注释】

  • 规范中 fulfilled 与部分资料里的 resolved 同指成功态。
  • 无法直接读 promise.status(ES202+ 有提案);用 then/catch 响应。

3.2 状态转换示例

状态变化演示:

javascript 复制代码
// 成功的 Promise
const successPromise = new Promise((resolve, reject) => {
    console.log('1. 初始状态: pending');
    
    setTimeout(() => {
        console.log('2. 调用 resolve,状态变为: fulfilled');
        resolve('成功结果');
    }, 1000);
});

successPromise.then(result => {
    console.log('3. 接收结果:', result);
});

// 失败的 Promise
const failPromise = new Promise((resolve, reject) => {
    console.log('1. 初始状态: pending');
    
    setTimeout(() => {
        console.log('2. 调用 reject,状态变为: rejected');
        reject(new Error('失败原因'));
    }, 1000);
});

failPromise.catch(error => {
    console.error('3. 接收错误:', error.message);
});

【代码注释】

  • resolve/reject 在异步回调里调用,状态在 then 注册之后才可能改变。
  • 对应 02-改变promise对象的状态.htmlreject 后再 resolve 无效。

3.3 状态检查

检查 Promise 状态:

javascript 复制代码
function checkPromiseState(promise) {
    // 注意:无法直接检查 Promise 的状态
    // Promise 的状态是内部的,不对外暴露
    // 我们只能通过 then() 和 catch() 来响应状态变化
    
    promise
        .then(() => {
            console.log('Promise 状态为: fulfilled');
        })
        .catch(() => {
            console.log('Promise 状态为: rejected');
        });
}

// 使用示例
const promise = new Promise((resolve) => {
    setTimeout(() => resolve('完成'), 1000);
});

checkPromiseState(promise);

【代码注释】

  • 演示:只能通过 then/catch 间接感知状态,不能轮询内部字段。
  • 调试可用 Promise.resolve(p).then(...).catch(...) 包装日志。

3.4 配套可运行示例(状态不可逆)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Promise 状态</title></head>
<body>
<script>
  const p = new Promise((resolve, reject) => {
    reject('失败');
    resolve('成功'); // 无效
  });
  p.then(v => console.log('then', v)).catch(e => console.log('catch', e));
</script>
</body>
</html>

【代码注释】

  • 对应 02-改变promise对象的状态.html;先 reject 则永远 rejected。

【本章小结】

状态 含义
pending 初始,未落定
fulfilled resolve 成功
rejected reject / throw 失败

【实战要点】

  • 状态只变一次;reject 后再 resolve 无效。
  • 不能轮询内部状态字段,用 then/catch 响应。

【面试考点】

Q1:Promise 有哪几种状态?能否从 fulfilled 回到 pending?

A:pending / fulfilled / rejected;不可逆。

3.5 配套可运行示例(pending → fulfilled)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>状态变化</title></head>
<body>
<script>
  const p = new Promise(resolve => setTimeout(() => resolve('完成'), 500));
  console.log('创建后立刻打印,仍为 pending');
  p.then(v => console.log('then 收到', v));
</script>
</body>
</html>

【代码注释】

  • 500ms 后 resolve;观察控制台先打 pending 提示,再打 then 结果。

4. Promise 基础语法

4.1 Promise 构造函数

创建 Promise 对象:

javascript 复制代码
// 基础创建方式
const promise1 = new Promise((resolve, reject) => {
    // executor 函数,在创建 Promise 时立即执行
    console.log('Promise 创建时立即执行');
    
    // 异步操作
    setTimeout(() => {
        resolve('成功');
    }, 1000);
});

// 包装异步操作
function readFileAsync(filename) {
    return new Promise((resolve, reject) => {
        // 模拟异步读取文件
        setTimeout(() => {
            const success = Math.random() > 0.3;
            
            if (success) {
                resolve(`文件 ${filename} 的内容`);
            } else {
                reject(new Error(`读取文件 ${filename} 失败`));
            }
        }, 1000);
    });
}

// 使用示例
readFileAsync('data.txt')
    .then(content => {
        console.log('文件内容:', content);
    })
    .catch(error => {
        console.error('读取失败:', error.message);
    });

【代码注释】

  • executor 立即执行;把 fs.readFile、XHR 等包进 Promise 是常见模式。
  • Node 现代写法用 fs.promisesutil.promisify,见 §6.4。

4.2 resolve 和 reject

resolve 和 reject 的使用:

javascript 复制代码
// resolve 的使用
const promise1 = new Promise((resolve) => {
    // resolve 可以传递各种类型的值
    resolve('字符串值');
    // resolve(123); // 数字
    // resolve({ name: '对象' }); // 对象
    // resolve([1, 2, 3]); // 数组
    // resolve(anotherPromise); // 另一个 Promise
});

// reject 的使用
const promise2 = new Promise((_, reject) => {
    // reject 通常传递 Error 对象
    reject(new Error('操作失败'));
    // reject('错误原因'); // 也可以传递其他类型
});

// 实际应用示例
function getUserData(userId) {
    return new Promise((resolve, reject) => {
        // 模拟 API 调用
        setTimeout(() => {
            if (userId > 0) {
                resolve({
                    id: userId,
                    name: '用户' + userId,
                    email: `user${userId}@example.com`
                });
            } else {
                reject(new Error('无效的用户ID'));
            }
        }, 1000);
    });
}

// 使用示例
getUserData(1)
    .then(user => {
        console.log('用户信息:', user);
    })
    .catch(error => {
        console.error('错误:', error.message);
    });

【代码注释】

  • resolve 可传任意类型作 PromiseResult;reject 建议用 Error 便于堆栈。
  • executor 里 throw 等价于 reject(err)

4.3 配套可运行示例(then 双回调)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>then 双参</title></head>
<body>
<script>
  const ok = new Promise(r => setTimeout(() => r('OK'), 200));
  const bad = new Promise((_, j) => setTimeout(() => j('ERR'), 200));
  ok.then(v => console.log('成功', v), e => console.log('失败', e));
  bad.then(v => console.log('成功', v), e => console.log('失败', e));
</script>
</body>
</html>

【代码注释】

  • 对应 03-为promise对象设置回调函数.html ;更推荐失败分支用 .catch()

【本章小结】

API 时机
executor new Promise 时同步执行
resolve/reject 异步结果落定

【实战要点】

  • 把 XHR、fs.readFile 包进 executor;Node 优先 fs.promises
  • executor 内 throw 等价 reject

【面试考点】

Q1:resolve 可以传什么?

A:任意类型;传 thenable 会按规范展开。


5. Promise 实例方法

5.1 then() 方法

5.1.1 配套可运行示例(基础 then)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>基础 then</title></head>
<body>
<script>
  new Promise((resolve) => setTimeout(() => resolve('数据'), 300))
    .then(v => console.log('成功', v))
    .catch(e => console.error('失败', e));
</script>
</body>
</html>

【代码注释】

  • 对应 03-为promise对象设置回调函数.html;then 在 resolve 后作为微任务执行。

then() 方法详解:

javascript 复制代码
// then() 方法的基础语法
promise.then(
    onFulfilled, // Promise 成功时的回调函数
    onRejected  // Promise 失败时的回调函数(可选)
);

// 基础使用
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功数据');
    }, 1000);
});

// 方式一:只处理成功情况
promise.then(result => {
    console.log('成功:', result);
});

// 方式二:同时处理成功和失败
promise.then(
    result => {
        console.log('成功:', result);
    },
    error => {
        console.error('失败:', error);
    }
);

// 方式三:链式调用,使用 catch() 处理错误
promise
    .then(result => {
        console.log('第一步成功:', result);
        return result + ' -> 处理';
    })
    .then(result => {
        console.log('第二步成功:', result);
    })
    .catch(error => {
        console.error('任何步骤失败:', error);
    });

【代码注释】

  • then 两个参数分别处理成功/失败;更推荐成功用 then、失败用 catch。
  • then 回调返回值决定下一个 then 收到的 Promise 状态与结果。

5.2 then() 返回值规则

then() 的返回值:

javascript 复制代码
// 规则1:返回普通值,状态变为 fulfilled
Promise.resolve(1)
    .then(value => {
        console.log('接收到:', value); // 1
        return value + 1; // 返回 2
    })
    .then(value => {
        console.log('接收到:', value); // 2
    });

// 规则2:返回 Promise,状态取决于返回的 Promise
Promise.resolve()
    .then(() => {
        return new Promise(resolve => {
            setTimeout(() => resolve('异步结果'), 1000);
        });
    })
    .then(value => {
        console.log('异步结果:', value);
    });

// 规则3:抛出错误,状态变为 rejected
Promise.resolve()
    .then(() => {
        throw new Error('手动抛出的错误');
    })
    .then(() => {
        console.log('不会执行');
    })
    .catch(error => {
        console.error('捕获错误:', error.message);
    });

// 规则4:没有返回值,等同于返回 undefined
Promise.resolve(1)
    .then(value => {
        console.log('处理:', value);
        // 没有 return 语句
    })
    .then(value => {
        console.log('接收到:', value); // undefined
    });

【代码注释】

  • 规范四种情况:无返回→undefined 成功;返回值→成功;返回 Promise→跟随;throw→失败。
  • 对应 02-01-then方法.html 对 then 返回 Promise 的实验。

5.3 catch() 方法

catch() 方法详解:

javascript 复制代码
// catch() 是 then(null, rejection) 的语法糖
const promise = new Promise((resolve, reject) => {
    reject(new Error('操作失败'));
});

// 方式一:使用 then 的第二个参数
promise.then(
    result => {
        console.log('成功:', result);
    },
    error => {
        console.error('失败:', error);
    }
);

// 方式二:使用 catch()(推荐)
promise
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        console.error('失败:', error);
    });

// catch() 的错误捕获特性
Promise.resolve()
    .then(() => {
        throw new Error('第一步错误');
    })
    .then(() => {
        console.log('不会执行');
    })
    .catch(error => {
        console.error('捕获错误:', error.message);
        return '继续执行';
    })
    .then(result => {
        console.log('继续执行:', result); // 会执行
    });

【代码注释】

  • catch 等价 then(null, rejection);可捕获链上任意 then 抛错。
  • catch 返回非 throw 的值会让后续 then 继续走成功分支。

5.4 finally() 方法

finally() 方法详解:

javascript 复制代码
// finally() 无论成功或失败都会执行
const promise = new Promise((resolve) => {
    setTimeout(() => resolve('成功'), 1000);
});

promise
    .then(result => {
        console.log('成功:', result);
        return result;
    })
    .catch(error => {
        console.error('失败:', error);
        throw error;
    })
    .finally(() => {
        console.log('清理资源,关闭连接');
        // finally 不接收参数,无法知道 Promise 的状态
    });

// 实际应用:加载状态管理
function loadDataWithLoading() {
    let isLoading = true;
    updateLoadingIndicator(isLoading);
    
    fetchData()
        .then(data => {
            displayData(data);
        })
        .catch(error => {
            showError(error);
        })
        .finally(() => {
            isLoading = false;
            updateLoadingIndicator(isLoading);
        });
}

function updateLoadingIndicator(loading) {
    if (loading) {
        console.log('显示加载指示器');
    } else {
        console.log('隐藏加载指示器');
    }
}

【代码注释】

  • finally 不接收结果参数,适合做关闭 loading、释放资源。
  • 对应 02-catch和finally.html;与 try/finally 语义类似。

5.5 配套可运行示例(then 返回值)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>then 返回值</title></head>
<body>
<script>
  Promise.resolve(1)
    .then(v => { console.log(1, v); return v + 1; })
    .then(v => { console.log(2, v); return new Promise(r => setTimeout(() => r(99), 200)); })
    .then(v => console.log(3, v));
</script>
</body>
</html>

【代码注释】

  • 对应 02-01-then方法.html;返回 Promise 时下一 then 等待其落定。

5.6 配套可运行示例(catch + finally)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>catch finally</title></head>
<body>
<script>
  Promise.reject('网络错误')
    .catch(e => { console.log('catch', e); return '默认值'; })
    .then(v => console.log('恢复', v))
    .finally(() => console.log('收尾'));
</script>
</body>
</html>

【代码注释】

  • catch 返回值让后续 then 走成功分支;finally 不接收结果参数。

【本章小结】

方法 作用
then 注册回调,返回新 Promise
catch 失败语法糖,可恢复链
finally 无论成败收尾

【实战要点】

  • then 返回普通值 / Promise / throw 决定下一节状态。
  • 推荐成功 then、失败统一 catch

【面试考点】

Q1:then 返回 undefined 时下一 then 收到什么?

A:fulfilled 且结果为 undefined


6. Promise 链式调用

6.1 链式调用原理

链式调用详解:

javascript 复制代码
// Promise 链式调用原理
Promise.resolve(1)
    .then(value => {
        console.log('第一步:', value); // 1
        return value + 1;
    })
    .then(value => {
        console.log('第二步:', value); // 2
        return value + 1;
    })
    .then(value => {
        console.log('第三步:', value); // 3
        return value + 1;
    })
    .then(value => {
        console.log('最终结果:', value); // 4
    });

// 链式调用解决回调地狱
// 传统回调方式:
asyncOperation1(data1 => {
    asyncOperation2(data1, data2 => {
        asyncOperation3(data2, data3 => {
            asyncOperation4(data3, data4 => {
                // 继续嵌套...
            });
        });
    });
});

// Promise 链式调用:
asyncOperation1()
    .then(data1 => asyncOperation2(data1))
    .then(data2 => asyncOperation3(data2))
    .then(data3 => asyncOperation4(data3))
    .then(data4 => {
        // 最终处理
    });

【代码注释】

  • 每个 then 返回新 Promise,才能 .then().then()
  • 对比嵌套 ajax 与 return ajaxPromise() 扁平链(§6.4)。

6.4 配套示例:Ajax 回调地狱 → Promise 链

6.4.2 配套可运行示例(链尾 catch)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>链尾 catch</title></head>
<body>
<script>
  Promise.resolve()
    .then(() => { throw new Error('中间出错'); })
    .then(() => console.log('不会执行'))
    .catch(e => console.log('链尾捕获', e.message));
</script>
</body>
</html>

【代码注释】

  • 对应 03-then和catch一起使用解决回调地狱.html 的异常穿透思路。

三步接口03-Ajax回调地狱 ):toplistplaylist/detailsong/detail

回调地狱结构:

javascript 复制代码
ajax({ url: '/toplist', success: res => {
    ajax({ url: '/playlist/detail?id=' + res.list[0].id, success: res2 => {
        ajax({ url: '/song/detail?ids=' + res2.privileges[0].id, success: res3 => {
            console.log(res3);
        }});
    }});
}});

【代码注释】

  • 嵌套 success 难以统一错误处理;依赖 Ajax 进阶篇 的 ajax.js

Promise 链(02 / 03 案例):

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Ajax Promise 链</title></head>
<body>
<script>
function ajaxPromise(options) {
  const { url, method = 'GET', headers = {}, body, dataType } = options;
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    if (dataType) xhr.responseType = dataType;
    xhr.onload = () => xhr.status === 200 ? resolve(xhr.response) : reject({ error: 1001 });
    xhr.onerror = () => reject({ error: 1002 });
    xhr.open(method, url);
    for (let k in headers) xhr.setRequestHeader(k, headers[k]);
    xhr.send(body);
  });
}
const API = 'http://api.fuming.site:54255';
ajaxPromise({ url: API + '/toplist', dataType: 'json' })
  .then(res => ajaxPromise({ url: API + '/playlist/detail?id=' + res.list[0].id, dataType: 'json' }))
  .then(res => ajaxPromise({ url: API + '/song/detail?ids=' + res.privileges[0].id, dataType: 'json' }))
  .then(res => console.log(res))
  .catch(() => console.log('数据获取失败!'));
</script>
</body>
</html>

【代码注释】

  • 每步 return ajaxPromise(...) 连接下一 then;03 在链尾用单一 catch(异常穿透)。
  • 01-Promise对象/ajax-promise.js 源码一致;API 需网络,可改 mock 练语法。

#mermaid-svg-S0TtuL5aTN1RCzkB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-S0TtuL5aTN1RCzkB .error-icon{fill:#552222;}#mermaid-svg-S0TtuL5aTN1RCzkB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-S0TtuL5aTN1RCzkB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-S0TtuL5aTN1RCzkB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-S0TtuL5aTN1RCzkB .marker.cross{stroke:#333333;}#mermaid-svg-S0TtuL5aTN1RCzkB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-S0TtuL5aTN1RCzkB p{margin:0;}#mermaid-svg-S0TtuL5aTN1RCzkB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB .cluster-label text{fill:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB .cluster-label span{color:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB .cluster-label span p{background-color:transparent;}#mermaid-svg-S0TtuL5aTN1RCzkB .label text,#mermaid-svg-S0TtuL5aTN1RCzkB span{fill:#333;color:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB .node rect,#mermaid-svg-S0TtuL5aTN1RCzkB .node circle,#mermaid-svg-S0TtuL5aTN1RCzkB .node ellipse,#mermaid-svg-S0TtuL5aTN1RCzkB .node polygon,#mermaid-svg-S0TtuL5aTN1RCzkB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-S0TtuL5aTN1RCzkB .rough-node .label text,#mermaid-svg-S0TtuL5aTN1RCzkB .node .label text,#mermaid-svg-S0TtuL5aTN1RCzkB .image-shape .label,#mermaid-svg-S0TtuL5aTN1RCzkB .icon-shape .label{text-anchor:middle;}#mermaid-svg-S0TtuL5aTN1RCzkB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-S0TtuL5aTN1RCzkB .rough-node .label,#mermaid-svg-S0TtuL5aTN1RCzkB .node .label,#mermaid-svg-S0TtuL5aTN1RCzkB .image-shape .label,#mermaid-svg-S0TtuL5aTN1RCzkB .icon-shape .label{text-align:center;}#mermaid-svg-S0TtuL5aTN1RCzkB .node.clickable{cursor:pointer;}#mermaid-svg-S0TtuL5aTN1RCzkB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-S0TtuL5aTN1RCzkB .arrowheadPath{fill:#333333;}#mermaid-svg-S0TtuL5aTN1RCzkB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-S0TtuL5aTN1RCzkB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-S0TtuL5aTN1RCzkB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S0TtuL5aTN1RCzkB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-S0TtuL5aTN1RCzkB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S0TtuL5aTN1RCzkB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-S0TtuL5aTN1RCzkB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-S0TtuL5aTN1RCzkB .cluster text{fill:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB .cluster span{color:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-S0TtuL5aTN1RCzkB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-S0TtuL5aTN1RCzkB rect.text{fill:none;stroke-width:0;}#mermaid-svg-S0TtuL5aTN1RCzkB .icon-shape,#mermaid-svg-S0TtuL5aTN1RCzkB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S0TtuL5aTN1RCzkB .icon-shape p,#mermaid-svg-S0TtuL5aTN1RCzkB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-S0TtuL5aTN1RCzkB .icon-shape .label rect,#mermaid-svg-S0TtuL5aTN1RCzkB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S0TtuL5aTN1RCzkB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-S0TtuL5aTN1RCzkB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-S0TtuL5aTN1RCzkB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ajaxPromise toplist
then 取 id
return playlist
then 取 songId
return song detail
then / catch

【代码注释】

  • 扁平链对应流程图;缺 return 会导致下一步收到 undefined。

【实战要点】

  • 链上必须 return Promise 或值;业务失败可 throw 进 catch。
  • HTTP 200 但业务 code 失败要在 then 里主动 reject

【面试考点】

  • then 返回 Promise 时下一个 then 如何等待?
  • 异常穿透:中间 then 抛错如何传到末尾 catch?

6.4.1 延伸练习:新歌榜前十首

配套 03 演示「榜单 → 歌单 → 单曲」三步链;作业 改为固定新歌榜 并输出前十首的歌名 + 演唱者

接口 URL
歌单详情 GET http://api.fuming.site:54255/playlist/detail?id=3779629
歌曲详情(批量) GET http://api.fuming.site:54255/song/detail?ids=id1,id2,...

思路: 先取歌单中前 10 个 privileges[].id,再一次性请求 song/detail,在 then 里遍历 songs 打印。

javascript 复制代码
const API = 'http://api.fuming.site:54255';

ajaxPromise({ url: API + '/playlist/detail?id=3779629', dataType: 'json' })
  .then(res => {
    const ids = res.privileges.slice(0, 10).map(p => p.id).join(',');
    return ajaxPromise({ url: API + '/song/detail?ids=' + ids, dataType: 'json' });
  })
  .then(res => {
    (res.songs || []).forEach((song, i) => {
      const artists = (song.ar || []).map(a => a.name).join(' / ');
      console.log(`${i + 1}. ${song.name} --- ${artists}`);
    });
  })
  .catch(() => console.log('数据获取失败'));

【代码注释】

  • 03/02-then链式调用 相同模式:return ajaxPromise 进入下一步;链尾 catch 统一失败提示。
  • ids 用逗号拼接为网易云 API 约定;不足 10 首时检查 privileges 长度。
  • 接口字段以实际 JSON 为准,可在 Network 面板对照 网易云音乐API.md 文档。
  • 作业不要求 DOM 渲染,控制台输出即可;扩展可做列表 UI(async/await 进阶 可用 async/await 改写)。

【实战要点】

  • 公共 API 可能限流或变更,练不通时先确认 03 三步链能否访问,再改作业 id。
  • 前十首若需并行请求(10 次 song/detail),应等 async/await 进阶 Promise.all ;本课用单次批量 ids 更合适。

6.5 配套示例:Node 顺序读文件

回调地狱(01-按照顺序读取文件-回调地狱写法.js):

javascript 复制代码
fs.readFile(path.resolve(__dirname, './data1.txt'), 'utf-8', (err, data) => {
  console.log('data1:', data);
  fs.readFile(path.resolve(__dirname, './data2.txt'), 'utf-8', (err, data) => {
    console.log('data2:', data);
    fs.readFile(path.resolve(__dirname, './data3.txt'), 'utf-8', (err, data) => {
      console.log('data3:', data);
    });
  });
});

【代码注释】

  • 三层 readFile 嵌套,错误需每层判断 err,维护成本高。
  • 02 的 Promise 链读同一组 data1.txt~data3.txt,便于对比输出。

Promise 链(02-按照顺序读取文件promise链式调用.js):

javascript 复制代码
const fs = require('node:fs/promises');
const path = require('path');

fs.readFile(path.resolve(__dirname, './data1.txt'), 'utf-8')
  .then(data => {
    console.log('data1:', data);
    return fs.readFile(path.resolve(__dirname, './data2.txt'), 'utf-8');
  })
  .then(data => {
    console.log('data2:', data);
    return fs.readFile(path.resolve(__dirname, './data3.txt'), 'utf-8');
  })
  .then(data => console.log('data3:', data))
  .catch(err => { throw err; });

【代码注释】

  • 对比 01 三层嵌套 fs.readFilereturn 下一读操作形成链。
  • Node 16+ 直接用 fs.promises,不必手写 promisify。
bash 复制代码
cd 04-Node回调地狱
node 01-按照顺序读取文件-回调地狱写法.js
node 02-按照顺序读取文件promise链式调用.js

【代码注释】

  • 04-Node回调地狱 目录执行;对比回调嵌套与 Promise 链输出。
  • 需 Node.js;data1.txtdata3.txt 与脚本同目录。

6.2 链式调用最佳实践

链式调用技巧:

javascript 复制代码
// 1. 每个 then() 返回值
Promise.resolve()
    .then(() => {
        return fetchData(); // 返回 Promise
    })
    .then(data => {
        return processData(data); // 返回处理后的数据
    })
    .then(result => {
        return saveData(result); // 返回保存结果
    });

// 2. 错误处理在链的末尾
Promise.resolve()
    .then(() => riskyOperation())
    .then(() => anotherRiskyOperation())
    .then(() => finalOperation())
    .catch(error => {
        // 统一错误处理
        handleError(error);
    });

// 3. 使用 finally() 进行清理
let resource;

Promise.resolve()
    .then(() => {
        resource = acquireResource();
        return useResource(resource);
    })
    .then(result => {
        return processResult(result);
    })
    .catch(error => {
        console.error('处理失败:', error);
    })
    .finally(() => {
        // 无论如何都要释放资源
        if (resource) {
            releaseResource(resource);
        }
    });

【代码注释】

  • 结合本节标题在控制台逐步执行;注意 executor 同步、then 异步。
  • 状态一旦改变不可再变;错误应沿链用 catch 统一处理。

6.3 错误传递机制

错误传递详解:

javascript 复制代码
// 错误会沿着链向下传递,直到被 catch() 捕获
Promise.resolve()
    .then(() => {
        throw new Error('第一步错误');
    })
    .then(() => {
        console.log('不会执行');
    })
    .then(() => {
        console.log('也不会执行');
    })
    .catch(error => {
        console.error('最终捕获:', error.message);
    });

// 在 catch() 中返回值可以继续链式调用
Promise.resolve()
    .then(() => {
        throw new Error('出错啦');
    })
    .catch(error => {
        console.error('捕获错误:', error.message);
        return '默认值'; // 返回默认值
    })
    .then(value => {
        console.log('继续执行:', value); // 会执行
    });

// catch() 中抛出错误会继续传递
Promise.resolve()
    .then(() => {
        throw new Error('第一个错误');
    })
    .catch(error => {
        console.error('第一个catch:', error.message);
        throw new Error('第二个错误'); // 抛出新的错误
    })
    .catch(error => {
        console.error('第二个catch:', error.message);
    });

【代码注释】

  • 结合本节标题在控制台逐步执行;注意 executor 同步、then 异步。
  • 状态一旦改变不可再变;错误应沿链用 catch 统一处理。

6.6 配套可运行示例(链式 mock)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>链式对比</title></head>
<body>
<script>
function step(n, ms) {
  return new Promise(r => setTimeout(() => r('step' + n), ms));
}
step(1, 100).then(v => { console.log(v); return step(2, 100); })
  .then(v => { console.log(v); return step(3, 100); })
  .then(v => console.log(v))
  .catch(e => console.error(e));
</script>
</body>
</html>

【代码注释】

  • 本地 mock 三步;对照 03-01-回调地狱写法.html 嵌套写法。

【本章小结】

模式 要点
链式 每步 return Promise 或值
Ajax return ajaxPromise 扁平多接口
Node fs.promises + return 下一读

【实战要点】

  • 链尾单一 catch;HTTP 200 但业务失败需主动 reject。
  • 延伸练习:歌单 3779629 → 批量 song/detail(§6.4.1)。

【面试考点】

Q1:链中间忘记 return 会怎样?

A:下一 then 收到 undefined,后续逻辑易错。


7. Promise 常见应用

7.1 网络请求封装

配套 ajax-promise.js(与 Ajax 进阶篇 ajax.js 对照):

javascript 复制代码
function ajaxPromise(options) {
    const { url, method = 'GET', headers = {}, body, dataType } = options;
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        if (dataType) xhr.responseType = dataType;
        xhr.onload = () => {
            if (xhr.status === 200) resolve(xhr.response);
            else reject({ error: 1001, msg: '未能获取到正确的内容!' });
        };
        xhr.onerror = () => reject({ error: 1002, msg: '未能成功发送请求!' });
        xhr.open(method, url);
        for (let key in headers) xhr.setRequestHeader(key, headers[key]);
        xhr.send(body);
    });
}

【代码注释】

  • 05-封装ajax promise 函数.html 引入的脚本一致;用 return new Promise 替代 success/error 回调。
  • 调用方:ajaxPromise({ url, dataType:'json' }).then(...).catch(...)
  • 链式多接口见 §6.4(02-then链式调用解决回调地狱03-then和catch一起使用)。

通用 httpGet 示例(理解原理):

javascript 复制代码
// 封装 XMLHttpRequest
function httpGet(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.open('GET', url);
        
        xhr.onload = function() {
            if (xhr.status === 200) {
                try {
                    const data = JSON.parse(xhr.responseText);
                    resolve(data);
                } catch (error) {
                    reject(error);
                }
            } else {
                reject(new Error(`HTTP ${xhr.status}`));
            }
        };
        
        xhr.onerror = function() {
            reject(new Error('网络错误'));
        };
        
        xhr.send();
    });
}

// 使用示例
httpGet('/api/users')
    .then(users => {
        console.log('用户列表:', users);
    })
    .catch(error => {
        console.error('获取失败:', error);
    });

// 封装 POST 请求
function httpPost(url, data) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.open('POST', url);
        xhr.setRequestHeader('Content-Type', 'application/json');
        
        xhr.onload = function() {
            if (xhr.status === 201) {
                try {
                    const response = JSON.parse(xhr.responseText);
                    resolve(response);
                } catch (error) {
                    reject(error);
                }
            } else {
                reject(new Error(`HTTP ${xhr.status}`));
            }
        };
        
        xhr.onerror = function() {
            reject(new Error('网络错误'));
        };
        
        xhr.send(JSON.stringify(data));
    });
}

// 使用示例
httpPost('/api/users', {
    name: '张三',
    email: 'zhangsan@example.com'
})
.then(response => {
    console.log('创建成功:', response);
})
.catch(error => {
    console.error('创建失败:', error);
});

【代码注释】

  • 配套 ajax-promise.js:200 resolve 响应体,否则 reject 自定义对象。
  • 与 Ajax 进阶篇 回调版 ajax({success,error}) 对照,接口改为返回 Promise。

7.2 定时器封装

配套 setTimeoutPromise(04-封装定时器Promise函数.html):

javascript 复制代码
function setTimeoutPromise(delay) {
    return new Promise(resolve => {
        setTimeout(resolve, delay);
    });
}
setTimeoutPromise(3000).then(() => console.log('Promise 版定时器执行了'));

【代码注释】

  • resolve 无参时 then 收到 undefined;延迟时间单位毫秒。
  • 原版 setTimeout 与 Promise 版可并行对比执行先后。
  • async/await 进阶 可写 await setTimeoutPromise(3000)

Promise 版本的 setTimeout(通用写法):

javascript 复制代码
// Promise 版本的 setTimeout
function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

// 使用示例
async function exampleWithDelay() {
    console.log('开始');
    
    await delay(1000);
    console.log('1秒后');
    
    await delay(2000);
    console.log('再过2秒');
}

// Promise 版本的 setInterval
function interval(ms, callback) {
    return new Promise(resolve => {
        const id = setInterval(() => {
            const result = callback();
            if (result === 'stop') {
                clearInterval(id);
                resolve();
            }
        }, ms);
    });
}

// 使用示例
async function timerExample() {
    let count = 0;
    
    await interval(1000, () => {
        count++;
        console.log('计数:', count);
        
        if (count >= 5) {
            console.log('停止计时');
            return 'stop';
        }
    });
    
    console.log('计时结束');
}

【代码注释】

  • new Promise(r => setTimeout(r, ms)) 是标准延迟模式。
  • 对应 04-封装定时器Promise函数.html

7.3 文件操作封装

Node.js 文件操作 Promise 化:

javascript 复制代码
const fs = require('fs');

// Promise 版本的 readFile
function readFilePromise(filename) {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

// Promise 版本的 writeFile
function writeFilePromise(filename, content) {
    return new Promise((resolve, reject) => {
        fs.writeFile(filename, content, 'utf8', (err) => {
            if (err) {
                reject(err);
            } else {
                resolve('文件写入成功');
            }
        });
    });
}

// 使用示例
async function processFile() {
    try {
        // 读取文件
        const content = await readFilePromise('input.txt');
        console.log('文件内容:', content);
        
        // 处理内容
        const processed = content.toUpperCase();
        
        // 写入文件
        await writeFilePromise('output.txt', processed);
        console.log('文件处理完成');
        
    } catch (error) {
        console.error('文件操作失败:', error);
    }
}

【代码注释】

  • 手动 promisify 教学用;Node 16+ 推荐 fs.promises.readFile(§6.4)。
  • 链式读文件避免三层嵌套回调。

7.4 配套可运行示例(定时器 Promise)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>定时器 Promise</title></head>
<body>
<script>
  function setTimeoutPromise(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  console.log('start');
  setTimeoutPromise(800).then(() => console.log('after 800ms'));
</script>
</body>
</html>

【代码注释】

  • 对应 04-封装定时器Promise函数.html ;async/await 可 await setTimeoutPromise(ms)

7.5 配套可运行示例(ajaxPromise 最小版)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>ajaxPromise</title></head>
<body>
<button id="btn">请求</button><pre id="out"></pre>
<script>
function ajaxPromise({ url, dataType }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    if (dataType) xhr.responseType = dataType;
    xhr.onload = () => xhr.status === 200 ? resolve(xhr.response) : reject('HTTP');
    xhr.onerror = () => reject('NET');
    xhr.open('GET', url);
    xhr.send();
  });
}
document.getElementById('btn').onclick = () => {
  ajaxPromise({ url: 'https://httpbin.org/get', dataType: 'json' })
    .then(d => out.textContent = JSON.stringify(d, null, 2))
    .catch(e => out.textContent = String(e));
};
const out = document.getElementById('out');
</script>
</body>
</html>

【代码注释】

  • 对应 05-封装ajax promise 函数.html;课堂 API 见 §6.4。

【本章小结】

封装 场景
ajaxPromise XHR → Promise
setTimeoutPromise 延迟
promisify Node 回调 → Promise

【实战要点】

  • ajax-promise.js 与回调版 ajax.js 对照学错误处理。
  • 定时器 Promise 是 await delay(ms) 的基础。

【面试考点】

Q1:如何把回调 API 改成 Promise?

A:在回调里调用 resolve/reject,并 return new Promise(...)


8. Promise 静态方法

8.1 Promise.resolve()

功能:返回一个 Promise,状态由参数决定(课程笔记 §4.1)。

javascript 复制代码
// 情况一:无参 → fulfilled,结果为 undefined
Promise.resolve().then(v => console.log(v)); // undefined

// 情况二:非 Promise、非 thenable 的值 → fulfilled,结果为该值
Promise.resolve(100).then(v => console.log(v)); // 100

// 情况三:参数已是 Promise → 原样返回(同一对象)
const p1 = new Promise((resolve, reject) => {
    Math.random() >= 0.5 ? resolve('ok') : reject('fail');
});
const p2 = Promise.resolve(p1); // 等价 const p2 = p1

// 情况四:thenable 对象(有 then 方法)→ 执行 then,按其 resolve/reject 定状态
const obj = {
    then(res, rej) {
        Math.floor(Math.random() * 10) >= 5 ? res(8) : rej(3);
    }
};
Promise.resolve(obj).then(v => console.log('成功', v)).catch(r => console.log('失败', r));

【代码注释】

  • 对应 05-01-resolve方法.html 四种注释情形。
  • thenable 常用于兼容第三方「类 Promise」对象。
  • Promise.resolve(p1) 不会多包一层,与 new Promise(r => r(p1)) 不同。

8.2 Promise.reject()

8.2.1 配套可运行示例(仅 reject)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Promise.reject</title></head>
<body>
<script>
  Promise.reject(new Error('直接失败'))
    .then(() => console.log('跳过'))
    .catch(e => console.log('catch', e.message));
</script>
</body>
</html>

【代码注释】

  • 对应 05-02-reject方法.html;无需 new Promise 即可得到 rejected 实例。
javascript 复制代码
Promise.reject('出错了')
    .then(() => console.log('不会执行'))
    .catch(reason => console.log('失败', reason)); // 出错了

【代码注释】

  • 直接得到 rejected 状态,PromiseResult 为参数。
  • 对应 05-02-reject方法.html ;等价 new Promise((_, rej) => rej('出错了'))
  • 规范中还有 Promise.all / race / allSettled 在后续课程展开,本篇 以 resolve/reject 为主。

【实战要点】

  • 已知同步成功值用 Promise.resolve(data)new Promise(r => r(data)) 更简洁。
  • 快速失败路径用 Promise.reject(err) 进入统一 catch。

8.3 配套可运行示例(Promise.resolve / reject)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>静态方法</title></head>
<body>
<script>
  Promise.resolve(100).then(v => console.log('resolve 100', v));
  Promise.resolve({ then(res) { res('thenable'); } }).then(v => console.log('thenable', v));
  Promise.reject('fail').catch(e => console.log('reject', e));
</script>
</body>
</html>

【代码注释】

  • 对应 05-01-resolve方法.html05-02-reject方法.html 核心情形。

【面试考点】

  • Promise.resolve(thenable)Promise.resolve(普通值) 区别?
  • Promise.reject 与 executor 里 reject 的关系?

【本章小结】

方法 结果
Promise.resolve 四种参数情形定状态
Promise.reject 直接 rejected

9. 调试与错误处理

9.1 错误处理最佳实践

全面的错误处理:

javascript 复制代码
// 1. 始终使用 catch() 处理错误
Promise.resolve()
    .then(() => riskyOperation())
    .catch(error => {
        console.error('捕获错误:', error);
    });

// 2. 在 catch() 中返回默认值
Promise.resolve()
    .then(() => riskyOperation())
    .catch(error => {
        console.error('操作失败,使用默认值:', error);
        return defaultValue; // 返回默认值
    })
    .then(value => {
        console.log('继续执行:', value);
    });

// 3. 区分错误类型
Promise.resolve()
    .then(() => riskyOperation())
    .catch(error => {
        if (error instanceof NetworkError) {
            console.error('网络错误:', error);
            return fallbackData();
        } else if (error instanceof ValidationError) {
            console.error('验证错误:', error);
            throw error; // 重新抛出验证错误
        } else {
            console.error('未知错误:', error);
            throw error;
        }
    });

// 4. 使用 finally() 进行清理
let connection;

Promise.resolve()
    .then(() => {
        connection = createConnection();
        return queryDatabase(connection);
    })
    .catch(error => {
        console.error('查询失败:', error);
    })
    .finally(() => {
        if (connection) {
            connection.close();
        }
    });

【代码注释】

  • 结合本节标题在控制台逐步执行;注意 executor 同步、then 异步。
  • 状态一旦改变不可再变;错误应沿链用 catch 统一处理。

9.2 调试技巧

Promise 调试方法:

javascript 复制代码
// 1. 在每个 then() 中添加日志
Promise.resolve()
    .then(value => {
        console.log('步骤1:', value);
        return processValue(value);
    })
    .then(value => {
        console.log('步骤2:', value);
        return processValue(value);
    })
    .then(value => {
        console.log('步骤3:', value);
        return processValue(value);
    });

// 2. 使用 Promise 包装器进行调试
function debugPromise(promise, name) {
    return promise
        .then(value => {
            console.log(`✅ ${name} 成功:`, value);
            return value;
        })
        .catch(error => {
            console.error(`❌ ${name} 失败:`, error);
            throw error;
        });
}

// 使用示例
debugPromise(fetchUser('123'), '获取用户')
    .then(user => {
        return debugPromise(fetchUserPosts(user.id), '获取文章');
    })
    .then(posts => {
        console.log('最终结果:', posts);
    });

// 3. 添加超时检测
function withTimeout(promise, timeout, name) {
    return Promise.race([
        promise,
        new Promise((_, reject) => {
            setTimeout(() => {
                reject(new Error(`${name} 超时`));
            }, timeout);
        })
    ]);
}

// 使用示例
withTimeout(fetchData(), 5000, '数据加载')
    .then(data => {
        console.log('数据:', data);
    })
    .catch(error => {
        console.error('错误:', error.message);
    });

【代码注释】

  • Promise.race 可做超时;注意竞态先完成者决定结果。
  • 未捕获的 reject 会触发 unhandledrejection,生产应始终 catch。

9.3 配套可运行示例(链式调试)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>调试链</title></head>
<body>
<script>
  function debugP(p, name) {
    return p.then(v => (console.log('✅', name, v), v))
            .catch(e => (console.error('❌', name, e), Promise.reject(e)));
  }
  debugP(Promise.resolve(1), 's1')
    .then(v => debugP(Promise.resolve(v + 1), 's2'))
    .then(v => console.log('done', v))
    .catch(() => {});
</script>
</body>
</html>

【代码注释】

  • 分步 log 定位链中失败节点;配合浏览器异步调用栈。

【本章小结】

实践 说明
统一 catch 链尾或 try/catch
调试 debugP / 分步 log
超时 Promise.race(了解)

【实战要点】

  • 未捕获 reject 触发 unhandledrejection
  • catch 里 return 可恢复,throw 继续传递。

【面试考点】

Q1:catch 里 return 与 throw 区别?

A:return 后续 then 成功;throw 交给下一 catch。


总结

高频面试题速查

要点
回调地狱 Promise 链 + return
executor 同步执行,resolve 一次
then 返回值 普通值 / Promise / throw
catch 穿透 + 可恢复链
ajaxPromise XHR 包 Promise
Promise.resolve 四种参数
微任务 then 先于 setTimeout

#mermaid-svg-xf2VVMHuHZROnbY1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xf2VVMHuHZROnbY1 .error-icon{fill:#552222;}#mermaid-svg-xf2VVMHuHZROnbY1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xf2VVMHuHZROnbY1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xf2VVMHuHZROnbY1 .marker.cross{stroke:#333333;}#mermaid-svg-xf2VVMHuHZROnbY1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xf2VVMHuHZROnbY1 p{margin:0;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge{stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 text{fill:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth--1{stroke-width:17;}#mermaid-svg-xf2VVMHuHZROnbY1 .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-0{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-0{stroke-width:14;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-1{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-1{stroke-width:11;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 text{fill:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-2{stroke-width:8;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-3{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-3{stroke-width:5;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-4{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-4{stroke-width:2;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-5{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-5{stroke-width:-1;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-6{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-6{stroke-width:-4;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-7{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-7{stroke-width:-7;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-8{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-8{stroke-width:-10;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-9{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-9{stroke-width:-13;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 polygon,#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 text{fill:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .node-icon-10{font-size:40px;color:black;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .edge-depth-10{stroke-width:-16;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled circle,#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:lightgray;}#mermaid-svg-xf2VVMHuHZROnbY1 .disabled text{fill:#efefef;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-root rect,#mermaid-svg-xf2VVMHuHZROnbY1 .section-root path,#mermaid-svg-xf2VVMHuHZROnbY1 .section-root circle,#mermaid-svg-xf2VVMHuHZROnbY1 .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-xf2VVMHuHZROnbY1 .section-root text{fill:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-root span{color:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .section-2 span{color:#ffffff;}#mermaid-svg-xf2VVMHuHZROnbY1 .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-xf2VVMHuHZROnbY1 .edge{fill:none;}#mermaid-svg-xf2VVMHuHZROnbY1 .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-xf2VVMHuHZROnbY1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本篇 Promise
基础
executor 同步
pending 三态
状态不可逆
实例方法
then 返回值规则
catch 穿透
finally 收尾
链式
Ajax 扁平化
Node fs 链
静态
Promise.resolve
Promise.reject
衔接
Ajax 进阶篇 ajax
async/await 进阶 async

【代码注释】

  • 概括 本篇 知识树与练习顺序。
  • executor 同步、then 微任务、链式 return 是三条主线。
  • 衔接 Ajax 进阶篇 ajax 与 async/await 进阶 async/await。
  • 配套 HTML/JS 按表 0.1 逐项练习。

知识主线:

  1. 异步痛点:回调地狱 → Promise 链 + 统一 catch。
  2. 状态机:pending → fulfilled / rejected,只变一次。
  3. then:返回新 Promise;返回 Promise 则「跟随」其状态。
  4. 实战ajax-promise.js、定时器 Promise、Node fs.promises 顺序读文件。
  5. 静态方法Promise.resolve / Promise.reject 四种参数情形。
  6. 下一步:async/await 进阶 async/await、Promise.all/race。

建议练习顺序 :见 §0.4 --- 010203(含 §6.4.1 作业 )→ 0405


相关资源:

相关推荐
甲维斯1 小时前
国产版“Codex”初体验,智谱ZCode很强啊!
前端·人工智能·ai编程
道友可好1 小时前
AI 怎么自己跑完一个 6 小时的任务?
前端·人工智能·后端
To_OC1 小时前
通义千问多模态生图踩坑记:我是如何把两个报错逐个干翻的
前端·aigc·vite
Bigfish_coding2 小时前
前端转agent-第一周【python】-02 FastAPI与Pydantic实战(TS/JS视角)
前端
秃头网友小李2 小时前
前端难点:Vue3 响应式遇上 Three.js / ECharts —— 为什么要用 shallowRef?
前端·vue.js
梦曦i2 小时前
Vite插件开发框架:14个实用插件与完整工具包
前端
KaMeidebaby2 小时前
卡梅德生物技术快报|biotin 生物素标记抗体全流程
前端·人工智能·算法·数据挖掘·数据分析
VitoChang2 小时前
前端也能快速入门后端! NestJS前台和后台的Auth认证
前端·后端
TheITSea2 小时前
一、React初体验:搭建、解析现代开发环境
前端·react.js·前端框架