编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

    • 基础入门

      • README
      • 入门介绍
      • 数据类型
      • 运算符
      • 函数
      • 面向对象
      • 标准库
      • 异步操作
        • 7.1 解决局限性
          • 7.1.4 宏任务和微任务
          • 7.1.5 Web Workers
          • 7.1.6 整体的总结
        • 7.3 异步操作模式
          • 7.3.1 回调函数
          • 7.3.2 事件监听
          • 7.3.3 发布/订阅
          • 7.3.4 async/await
          • 7.3.5 异步模式的技术演变
        • 7.4 异步流程控制
          • 7.4.2 串行执行
          • 7.4.3 并行执行
          • 7.4.4 并行与串行结合
        • 7.5 定时器
          • 7.5.1 setTimeout
          • 7.5.2 setInterval
          • 7.5.3 取消定时器
          • 7.5.4 定时器执行机制
          • 7.5.5 注意事项
          • 7.5.6 定时器总结
          • 7.5.7 思考一个问题
        • 7.6 Promise
          • 7.6.1 基本概念
          • 7.6.2 创建Promise
          • 7.6.3 使用Promise
          • 7.6.4 Promise链式调用
          • 7.6.5 Promise静态方法
          • 7.6.6 async/await
          • 7.6.7 Promise总结
          • 7.6.8 理解微任务
          • 7.6.10 Promise的底层实现原理
      • 事件设计
      • 错误机制
      • 模块开发
      • 字符串处理
      • 迭代器与生成器
      • Symbol
      • DOM操作
      • 网络请求
    • 综合案例

    • 专栏博客

  • CodeX
  • JavaScript入门
  • 基础入门
杨充
2025-09-19
目录

异步操作

# 07.异步操作

# 目录介绍

  • 7.2 解决局限性
    • 7.2.4 宏任务和微任务
    • 7.2.5 Web Workers
    • 7.2.6 整体的总结
  • 7.3 异步操作模式
    • 7.3.1 回调函数
    • 7.3.2 事件监听
    • 7.3.3 发布/订阅
    • 7.3.4 async/await
  • 7.4 异步流程控制
    • 7.4.1 如何保证异步顺序
    • 7.4.2 串行执行
    • 7.4.3 并行执行
    • 7.4.4 并行与串行结合
  • 7.5 定时器
    • 7.5.1 setTimeout
    • 7.5.2 setInterval
    • 7.5.3 取消定时器
    • 7.5.4 定时器执行机制
    • 7.5.5 注意事项
    • 7.5.6 定时器总结
    • 7.5.7 思考一个问题
  • 7.6 Promise
    • 7.6.1 基本概念
    • 7.6.2 创建Promise
    • 7.6.3 使用Promise
    • 7.6.4 Promise链式调用
    • 7.6.5 Promise静态方法
    • 7.6.6 async/await
    • 7.6.7 Promise总结
    • 7.6.8 理解微任务

# 7.1 解决局限性

JavaScript 单线程与事件循环的底层原理:JavaScript 采用单线程模型,所有代码在一个主线程上执行。这是因为 JavaScript 最初设计用于操作 DOM,多线程同时修改 DOM 会导致竞态条件。为了实现非阻塞 I/O,JavaScript 引擎配合事件循环(Event Loop) 工作:主线程执行同步代码(调用栈)→ 同步代码执行完毕 → 清空微任务队列(Promise.then、MutationObserver、queueMicrotask)→ 从宏任务队列(setTimeout、setInterval、I/O、UI 渲染)中取出一个任务执行 → 重复。关键点:微任务在每个宏任务之后、下一个宏任务之前全部执行完毕,这就是为什么 Promise.then 总是比 setTimeout 先执行。

# 7.1.4 宏任务和微任务

在 JavaScript 中,异步任务分为两种:

  1. 宏任务(Macro Task)
  • 包括 setTimeout、setInterval、I/O 操作、UI 渲染等。
  • 宏任务会被放入宏任务队列,等待事件循环处理。
  1. 微任务(Micro Task)
  • 包括 Promise、MutationObserver 等。
  • 微任务会被放入微任务队列,在当前宏任务执行完毕后立即执行。

# 7.1.5 Web Workers

为了充分利用多核 CPU 的性能,JavaScript 提供了 Web Workers:

多线程支持:Web Workers 允许在后台运行脚本,独立于主线程。

通信机制:通过 postMessage 和 onmessage 实现主线程与 Worker 线程之间的通信。

限制:Worker 线程不能直接操作 DOM。

# 7.1.6 整体的总结

特性 说明
单线程模型 JavaScript 只有一个主线程执行代码。
异步编程 通过回调函数、Promise、async/await 实现非阻塞操作。
事件循环 管理调用栈、任务队列和微任务队列,确保异步任务有序执行。
宏任务与微任务 宏任务包括 setTimeout、setInterval,微任务包括 Promise。
Web Workers 允许在后台运行脚本,突破单线程限制。

# 7.3 异步操作模式

异步编程,下面总结一下异步操作的几种模式。

  • 非阻塞操作:通过异步 API(如 setTimeout、Promise、fetch 等),将耗时任务放到后台执行,避免阻塞主线程。
  • 回调函数:异步任务完成后,通过回调函数通知主线程。

# 7.3.1 回调函数

回调函数是异步编程的最基本方式,将函数作为参数传递给异步操作,操作完成后调用该函数。

function fetchData(callback) {
  setTimeout(() => {
    const data = 'Some data';
    callback(data);
  }, 1000);
}

fetchData((data) => {
  console.log(data); // 1秒后输出: Some data
});
1
2
3
4
5
6
7
8
9
10

缺点:

  • 回调地狱(Callback Hell):多层嵌套回调导致代码难以维护。
  • 错误处理不便。

# 7.3.2 事件监听

另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的 jQuery 的写法 (opens new window))。

f1.on('done', f2);
1

上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:

function f1() {
  setTimeout(function () {
    // ...
    f1.trigger('done');
  }, 1000);
}
1
2
3
4
5
6

上面代码中,f1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行f2。

  1. 优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。
  2. 缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。

# 7.3.3 发布/订阅

事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。

首先,f2向信号中心jQuery订阅done信号。

jQuery.subscribe('done', f2);
1

然后,f1进行如下改写。

function f1() {
  setTimeout(function () {
    // ...
    jQuery.publish('done');
  }, 1000);
}
1
2
3
4
5
6

上面代码中,jQuery.publish('done')的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。

f2完成执行后,可以取消订阅(unsubscribe)。

jQuery.unsubscribe('done', f2);
1

这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

# 7.3.4 async/await

async/await 是 ES8 引入的语法糖,基于 Promise,使异步代码看起来像同步代码。

async function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = 'Some data';
      resolve(data);
    }, 1000);
  });
}

async function main() {
  try {
    const data = await fetchData();
    console.log(data); // 1秒后输出: Some data
  } catch (error) {
    console.error(error);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

优点:1.代码更简洁,易于阅读和维护。2.结合 try-catch 实现错误处理。

# 7.3.5 异步模式的技术演变

疑惑→答疑→论证→结果展示:

疑惑:为什么 JavaScript 的异步编程经历了回调 → Promise → async/await 的演变?

答疑:每一代方案都在解决前一代的痛点——回调地狱的可读性、错误处理的一致性、代码的同步化表达。

论证:

// 第1代:回调函数(ES3 时代)
// 问题:回调地狱、错误处理分散
getUser(userId, (err, user) => {
  if (err) return handleError(err);
  getProfile(user.id, (err, profile) => {
    if (err) return handleError(err);
    // 继续嵌套...
  });
});

// 第2代:Promise(ES6/2015)
// 解决:链式调用扁平化、统一错误处理
getUser(userId)
  .then(user => getProfile(user.id))
  .then(profile => render(profile))
  .catch(err => handleError(err));

// 第3代:async/await(ES2017)
// 解决:代码看起来像同步、try/catch 捕获错误
async function loadProfile(userId) {
  try {
    const user = await getUser(userId);
    const profile = await getProfile(user.id);
    render(profile);
  } catch (err) {
    handleError(err);
  }
}

// 第4代:顶层 await(ES2022)
// 模块可以直接使用 await
const response = await fetch('/api/config');
const config = await response.json();
export default config;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

结果展示:每一代的核心进步——

特性 回调 Promise async/await
代码结构 嵌套金字塔 链式调用 顺序同步风格
错误处理 每层单独处理 .catch() 统一 try/catch
并行控制 手动计数 Promise.all await Promise.all
取消支持 无 无原生支持 AbortController

# 7.4 异步流程控制

# 7.4.2 串行执行

# 7.4.3 并行执行

流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final函数。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

items.forEach(function(item) {
  async(item, function(result){
    results.push(result);
    if(results.length === items.length) {
      final(results[results.length - 1]);
    }
  })
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面代码中,forEach方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final函数。

相比而言,上面的写法只要一秒,就能完成整个脚本。这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有了第三种流程控制方式。

# 7.4.4 并行与串行结合

所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

function launcher() {
  while(running < limit && items.length > 0) {
    var item = items.shift();
    async(item, function(result) {
      results.push(result);
      running--;
      if(items.length > 0) {
        launcher();
      } else if(running === 0) {
        final(results);
      }
    });
    running++;
  }
}

launcher();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

上面代码中,最多只能同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。

这段代码需要三秒完成整个脚本,处在串行执行和并行执行之间。通过调节limit变量,达到效率和资源的最佳平衡。

# 7.5 定时器

JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成。它们向任务队列添加定时任务。

# 7.5.1 setTimeout

setTimeout 用于在指定的延迟时间后执行一次回调函数。

语法

setTimeout(callback, delay, arg1, arg2, ...);
1

参数

  • callback:要执行的函数。
  • delay:延迟时间,以毫秒为单位(默认值为 0)。
  • arg1, arg2, ...:传递给回调函数的参数(可选)。

返回值

  • 返回一个唯一的定时器 ID,可用于取消定时器。

示例

setTimeout(() => {
    console.log("Hello after 2 seconds!");
}, 2000);
1
2
3

再来看一个案例

console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);
// 1
// 3
// 2
1
2
3
4
5
6

# 7.5.2 setInterval

setInterval 用于以固定的时间间隔重复执行回调函数。

语法

setInterval(callback, delay, arg1, arg2, ...);
1

参数

  • callback:要执行的函数。
  • delay:每次执行的时间间隔,以毫秒为单位。
  • arg1, arg2, ...:传递给回调函数的参数(可选)。

返回值

  • 返回一个唯一的定时器 ID,可用于取消定时器。

示例

let count = 0;
const intervalId = setInterval(() => {
    count++;
    console.log(`Interval count: ${count}`);
    if (count === 5) {
        clearInterval(intervalId); // 停止定时器
    }
}, 1000);
1
2
3
4
5
6
7
8

# 7.5.3 取消定时器

可以使用 clearTimeout 和 clearInterval 来取消已设置的定时器。

clearTimeout

const timeoutId = setTimeout(() => {
    console.log("This will not run");
}, 2000);

clearTimeout(timeoutId); // 取消定时器
1
2
3
4
5

clearInterval

const intervalId = setInterval(() => {
    console.log("This will not run");
}, 1000);

clearInterval(intervalId); // 取消定时器
1
2
3
4
5

# 7.5.4 定时器执行机制

  • 异步执行:定时器的回调函数是异步执行的,即使延迟时间为 0,也会被放入任务队列,等待调用栈清空后执行。
  • 最小延迟时间:在浏览器中,定时器的最小延迟时间通常为 4 毫秒(即使设置为 0)。
  • 事件循环:定时器的回调函数属于宏任务,会在事件循环的宏任务阶段执行。

setTimeout和setInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。

这意味着,setTimeout和setInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。

setTimeout(someTask, 100);
veryLongTask();
1
2

上面代码的setTimeout,指定100毫秒以后运行一个任务。但是,如果后面的veryLongTask函数(同步任务)运行时间非常长,过了100毫秒还无法结束,那么被推迟运行的someTask就只有等着,等到veryLongTask运行结束,才轮到它执行。

再看一个setInterval的例子。

setInterval(function () {
  console.log(2);
}, 1000);

sleep(3000);

function sleep(ms) {
  var start = Date.now();
  while ((Date.now() - start) < ms) {
  }
}
1
2
3
4
5
6
7
8
9
10
11

上面代码中,setInterval要求每隔1000毫秒,就输出一个2。但是,紧接着的sleep语句需要3000毫秒才能完成,那么setInterval就必须推迟到3000毫秒之后才开始生效。注意,生效后setInterval不会产生累积效应,即不会一下子输出三个2,而是只会输出一个2。

# 7.5.5 注意事项

  1. 定时器不精确:定时器的执行时间可能会受到其他任务的影响,因此不能保证完全精确。
  2. 避免过度使用 setInterval:如果回调函数的执行时间超过间隔时间,可能会导致多个回调函数堆积。可以使用 setTimeout 递归调用来替代。
  3. 内存泄漏:如果定时器未被清除,可能会导致内存泄漏。确保在不需要时调用 clearTimeout 或 clearInterval。

# 7.5.6 定时器总结

函数 说明 取消方法
setTimeout 在指定延迟后执行一次回调函数。 clearTimeout
setInterval 以固定时间间隔重复执行回调函数。 clearInterval

# 7.5.7 思考一个问题

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0),那么会立刻执行吗?

答案是不会。因为上一节说过,必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会在下一轮事件循环一开始就执行。

setTimeout(function () {
  console.log(1);
}, 0);
console.log(2);
// 2
// 1
1
2
3
4
5
6

上面代码先输出2,再输出1。因为2是同步任务,在本轮事件循环执行,而1是下一轮事件循环执行。

总之,setTimeout(f, 0)这种写法的目的是,尽可能早地执行f,但是并不能保证立刻就执行f。

实际上,setTimeout(f, 0)不会真的在0毫秒之后运行,不同的浏览器有不同的实现。以 Edge 浏览器为例,会等到4毫秒之后运行。如果电脑正在使用电池供电,会等到16毫秒之后运行;如果网页不在当前 Tab 页,会推迟到1000毫秒(1秒)之后运行。这样是为了节省系统资源。

# 7.6 Promise

Promise 是 JavaScript 中用于处理异步操作的对象。它提供了一种更优雅的方式来管理异步代码,避免了传统的回调地狱(Callback Hell)。Promise 表示一个异步操作的最终完成(或失败)及其结果值。

// 传统写法
step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // ...
      });
    });
  });
});

// Promise 的写法
(new Promise(step1))
  .then(step2)
  .then(step3)
  .then(step4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

从上面代码可以看到,采用 Promises 以后,程序流程变得非常清楚,十分易读。注意,为了便于理解,上面代码的Promise实例的生成格式,做了简化,真正的语法请参照下文。

总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promise 就是解决这个问题,使得异步流程可以写成同步流程。

Promise 原本只是社区提出的一个构想,一些函数库率先实现了这个功能。ECMAScript 6 将其写入语言标准,目前 JavaScript 原生支持 Promise 对象。

# 7.6.1 基本概念

状态:Promise 有三种状态:

  • Pending(进行中):初始状态,既不是成功,也不是失败。
  • Fulfilled(已成功):操作成功完成。
  • Rejected(已失败):操作失败。

不可逆性:Promise 的状态一旦从 Pending 变为 Fulfilled 或 Rejected,就不能再改变。

这三种的状态的变化途径只有两种。 1.从“未完成”到“成功” ;2.从“未完成”到“失败”

一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

因此,Promise 的最终结果只有两种。

  • 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
  • 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。

# 7.6.2 创建Promise

使用 Promise 构造函数创建一个 Promise 对象。Promise 构造函数接受一个函数作为参数,该函数有两个参数:resolve 和 reject。

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve("Operation succeeded!");    // 将状态改为 Fulfilled,并传递结果值
        } else {
            reject("Operation failed!");        // 将状态改为 Rejected,并传递错误信息
        }
    }, 1000);
});
1
2
3
4
5
6
7
8
9
10

# 7.6.3 使用Promise

通过 then、catch 和 finally 方法来处理 Promise 的结果。

then

  • 用于处理 Promise 的成功状态(Fulfilled)。
  • 接收两个参数: 第一个参数是成功回调函数,接收 resolve 传递的值。第二个参数是失败回调函数(可选),接收 reject 传递的错误。

catch

  • 用于处理 Promise 的失败状态(Rejected)。
  • 相当于 then(null, rejectionCallback)。

finally

  • 无论 Promise 成功还是失败,都会执行的回调函数。

示例

promise
    .then((result) => {
        console.log(result); // "Operation succeeded!"
    })
    .catch((error) => {
        console.error(error); // "Operation failed!"
    })
    .finally(() => {
        console.log("Operation completed."); // 无论成功或失败都会执行
    });
1
2
3
4
5
6
7
8
9
10

# 7.6.4 Promise链式调用

then 方法返回一个新的 Promise,因此可以链式调用。示例

new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
})
    .then((result) => {
        console.log(result); // 1
        return result * 2;
    })
    .then((result) => {
        console.log(result); // 2
        return result * 3;
    })
    .then((result) => {
        console.log(result); // 6
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7.6.5 Promise静态方法

Promise 提供了一些静态方法,用于处理多个 Promise。

Promise.resolve。返回一个已成功的 Promise。

const resolvedPromise = Promise.resolve("Success!");
resolvedPromise.then((result) => {
  console.log(result); // "Success"
});
1
2
3
4

Promise.reject。返回一个已失败的 Promise。

const rejectedPromise = Promise.reject("Rejected!");
rejectedPromise.catch((error) => {
  console.error(error); // Rejected!
});
1
2
3
4

Promise.all。接收一个 Promise 数组,当所有 Promise 都成功时返回结果数组;如果有一个失败,则立即返回失败。

Promise.all([
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3),
])
    .then((results) => console.log(results)) // [1, 2, 3]
    .catch((error) => console.error(error));
1
2
3
4
5
6
7

Promise.race。接收一个 Promise 数组,返回第一个完成(无论成功或失败)的 Promise 的结果。

Promise.race([
    new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
    new Promise((resolve) => setTimeout(() => resolve(2), 500)),
])
    .then((result) => console.log(result)) // 2
    .catch((error) => console.error(error));
1
2
3
4
5
6

Promise.allSettled。接收一个 Promise 数组,等待所有 Promise 完成(无论成功或失败),返回结果数组。

Promise.allSettled([
    Promise.resolve(1),
    Promise.reject("Error"),
])
    .then((results) => console.log(results));
// 输出:
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: 'Error' }
// ]
1
2
3
4
5
6
7
8
9
10

# 7.6.6 async/await

异步函数与 async/await,async/await 是基于 Promise 的语法糖,使异步代码看起来像同步代码。

async 函数,声明一个异步函数,返回一个 Promise。

async function fetchData() {
    return "Data";
}
fetchData().then((result) => console.log(result)); // "Data"
1
2
3
4

await 关键字,用于等待 Promise 完成,只能在 async 函数中使用。

async function fetchData() {
    const result = await new Promise((resolve) => setTimeout(() => resolve("Data"), 1000));
    console.log(result); // "Data"
}
fetchData();
1
2
3
4
5

顺序执行:await 会暂停当前函数的执行,直到 Promise 完成。

# 7.6.7 Promise总结

方法/特性 说明
Promise 构造函数 创建一个 Promise 对象。
then 处理 Promise 的成功状态。
catch 处理 Promise 的失败状态。
finally 无论成功或失败都会执行的回调函数。
Promise.resolve 返回一个已成功的 Promise。
Promise.reject 返回一个已失败的 Promise。
Promise.all 等待所有 Promise 成功,或第一个失败。
Promise.race 返回第一个完成的 Promise。
Promise.allSettled 等待所有 Promise 完成,无论成功或失败。
async/await 使异步代码看起来像同步代码。

# 7.6.8 理解微任务

Promise 的回调函数属于异步任务,会在同步任务之后执行。

new Promise(function (resolve, reject) {
  resolve(1);
}).then(console.log);

console.log(2);
// 2
// 1
1
2
3
4
5
6
7

上面代码会先输出2,再输出1。因为console.log(2)是同步任务,而then的回调函数属于异步任务,一定晚于同步任务执行。

但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。

setTimeout(function() {
  console.log(1);
}, 0);

new Promise(function (resolve, reject) {
  resolve(2);
}).then(console.log);

console.log(3);
// 3
// 2
// 1
1
2
3
4
5
6
7
8
9
10
11
12

上面代码的输出结果是321。这说明then的回调函数的执行时间,早于setTimeout(fn, 0)。因为then是本轮事件循环执行,setTimeout(fn, 0)在下一轮事件循环开始时执行。

# 7.6.10 Promise的底层实现原理

疑惑:Promise 内部是怎么实现的?.then() 是如何做到链式调用的?

答疑:Promise 的核心是一个状态机(pending → fulfilled/rejected)加上回调队列。每次调用 .then() 都会返回一个新的 Promise,这个新 Promise 的决议取决于回调函数的返回值。

论证——简化版 Promise 实现:

class SimplePromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.callbacks = [];
    
    const resolve = (value) => {
      if (this.state !== 'pending') return;
      this.state = 'fulfilled';
      this.value = value;
      this.callbacks.forEach(cb => cb.onFulfilled(value));
    };
    
    const reject = (reason) => {
      if (this.state !== 'pending') return;
      this.state = 'rejected';
      this.value = reason;
      this.callbacks.forEach(cb => cb.onRejected(reason));
    };
    
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }
  
  then(onFulfilled, onRejected) {
    return new SimplePromise((resolve, reject) => {
      const handle = (callback, fallback) => {
        try {
          const fn = typeof callback === 'function' ? callback : fallback;
          const result = fn(this.value);
          if (result instanceof SimplePromise) {
            result.then(resolve, reject); // 如果返回 Promise,等待其决议
          } else {
            resolve(result);
          }
        } catch (e) {
          reject(e);
        }
      };
      
      if (this.state === 'fulfilled') {
        queueMicrotask(() => handle(onFulfilled, v => v));
      } else if (this.state === 'rejected') {
        queueMicrotask(() => handle(onRejected, v => { throw v; }));
      } else {
        this.callbacks.push({
          onFulfilled: () => handle(onFulfilled, v => v),
          onRejected: () => handle(onRejected, v => { throw v; })
        });
      }
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

结果展示:.then() 返回新 Promise 是链式调用的关键。回调函数的返回值决定了新 Promise 的决议值;如果返回值是 Promise,则新 Promise "跟随"它的状态。

上次更新: 2026/06/10, 11:13:41
标准库
事件设计

← 标准库 事件设计→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式