Promise 实现 JS 异步编程

[!quote] promise 用作什么事情?
要创建一个进程,其中一个函数只有在另一个函数完成后才会触发,请参见关于 Promise 的文档。
——Window:setTimeout() 方法 - Web API | MDN
基本结构
1 | const promise = new Promise((resolve, reject) => { |
对上面这段代码详细的事件循环分析,请见 用事件循环详细分析一个简单的 promise 案例
- 更常见的一种结构:放在一个函数里作为返回值。
1 | const axios = (params) => { |
很多库之类的都是基于这个结构封装。
Promise 内部
promise 用于处理异步函数,有几个关键点:
-
Promise
的构造函数new Promise(executor)
中的executor
函数(resolve, reject) => { ... }
是立即同步执行的。 -
promise 内部一般会使用两个方法:
-
resolve()
成功时候使用,对应 已兑现(fulfilled) 状态 -
reject()
失败时候使用,对应 已拒绝(rejected) 状态
在这两个方法使用之前,promise 会处在 待定(pending) 状态。
在 Promise 内部一旦执行到这两个方法其中的一句,promise 的状态就会被敲定且不可再被更改。敲定以后,之前注册的 .then 或者 .catch 里的代码会立即被放入微任务队列里等待执行。
因为这个原因,使用 Promise 实现了异步转同步:
-
把耗时的任务放进 Promise 内部,先不关心它到底能不能成功执行,而是继续执行其他的同步代码;
-
等到耗时的任务执行完毕,自然会触发后面的 .then() 或者 .catch() 。再结合多个 .then() 的链式调用(因为其中一个敲定后才会触发后一个),就可以把异步任务转成同步任务理解了,如此一来还解决回调地狱(callback hell)的问题。
.then 和 .catch
- 成功或失败以后做什么?
刚刚说到,创建对象实例的时候,promise 里的代码会立刻执行。之后在成功或者失败后,我们会在 resolve() 或者 reject() 里放数据。那这个数据有什么用呢?
创建的实例会有 .then()
和 .catch()
两个方法,用于定义上文 promise 内部的函数执行成功或者失败之后,程序需要执行的后续操作。
.then()
方法里可以定义一个形参,这个形参等于上文中resolve()
括号里的东西。resolve()
括号里一般会塞关键的结果数据。.catch()
里的形参等于上文中reject()
括号里的东西,这里面什么都能塞,如果塞的是字符串,那后面.catch(e)
里的形参e
也会是一个字符串。但是尽管如此,通常塞一个Error
对象更合适。
在成功或者失败后,.then()
或者 .catch()
里的回调就会被放进微任务队列等待执行。
promise 巧妙的点就在于此,就像埋下了一个约定或者承诺,并且提前说好履约后会怎样,如此一来就不需要等待了,从而实现异步。
实例代码:
1 | function fetchData(success = true) { |
链式调用
上面的代码中可以看到居然有多个 .then() 语句,他们之间的关系是什么?
我们用一个“接力赛”的比喻来理解。每个 .then()
都是赛道上的一站,它接收上一站传来的“接力棒”(数据),处理完后,再把一个新的“接力棒”传给下一站。
[!tip] 试想
每一段接力跑是耗时的任务,就像实际开发中的网络请求一样。把异步转同步的关键就在于设下一个个“接力站”,一段跑完成了才开始下一段跑,如此一来一整段赛跑看起来就变成了一个同步任务一样。
核心原则
promise.then()
本身会返回一个全新的 Promise。
这个新 Promise 的状态(是成功 fulfilled
还是失败 rejected
)以及它的最终值,完全由你在 .then()
的回调函数中的 return
语句来决定。
场景 1: 返回一个基本数据类型或对象 (非 Promise 值) 或者 不返回
这是最简单、最常见的情况。
关系:
当你从一个 .then()
回调中返回一个普通的值(如数字、字符串、布尔值、null
、undefined
或一个普通对象),这个值会被“包装”成一个立即成功的 Promise。然后,这个“包装”好的值会作为参数,传递给下一个 .then()
的回调函数。
[!note] 总结
返回值是普通值或者没有 return 语句的时候,下一个.then
会立即接收到上一个.then
的返回值作为参数(即使是 undefined)。
代码示例:
1 | Promise.resolve(10) // 初始Promise,成功状态,值为 10 |
场景 2: 返回一个新的 Promise
这是实现异步操作串联转为同步的核心。
关系:
如果一个 .then()
回调返回了一个新的 Promise,那么整个 Promise 链会“暂停”,等待这个新的 Promise 状态落定(即 fulfilled
或 rejected
)。
[!warning] 这里的”暂停“是关键
- 如果新的 Promise 成功 (
fulfilled
):它的成功值(resolve()
里的玩意)会作为参数,传递给链中的下一个.then()
。 - 如果新的 Promise 失败 (
rejected
):它的失败原因会传递给链中的下一个.catch()
(或.then
的第二个参数)。
代码示例:
1 | Promise.resolve("开始处理") |
总结:下一个 .then
的执行时机和接收到的值,完全由上一个 .then
返回的那个新 Promise 决定。这就是 Promise 链能够“等待”异步操作完成的原理。
场景 3: 返回一个函数
关系:
在 JavaScript 中,函数也是一种对象。因此,返回一个函数和返回一个普通对象遵循同样的规则(场景 1)。函数本身会被当作一个值,传递给下一个 .then
,而不会被执行。
[!note] 总结
返回一个函数时,下一个.then
接收到的参数是这个函数本身,而不是它的执行结果。
代码示例:
1 | Promise.resolve() |
总结表格
在 .then 中返回… |
行为描述 | 下一个 .then 的回调函数… |
---|---|---|
基本数据类型或对象 (e.g., 123 , 'abc' , {} ) |
值被“包装”在一个立即成功的 Promise 中。 | 立即执行,并接收这个值作为参数。 |
undefined (无 return 语句) |
相当于 return undefined; ,遵循上一条规则。 |
立即执行,并接收 undefined 作为参数。 |
一个新的 Promise | 整个链条会等待这个新的 Promise 状态落定。 | 在新 Promise 成功后执行,并接收其成功值作为参数。 |
一个函数 | 函数本身被视为一个普通值。 | 立即执行,并接收这个函数本身作为参数(而不是函数的执行结果)。 |
throw new Error() 或一个被拒绝的 Promise |
链条中断,寻找错误处理。 | 不会执行,执行会跳转到最近的 .catch() 或 .then(null, onRejected) 。 |
理解这个机制是使用 Promise 和 async/await
(它其实是 Promise 的语法糖)构建健壮异步代码的基础。
- 标题: Promise 实现 JS 异步编程
- 作者: 三葉Leaves
- 创建于 : 2025-06-17 00:00:00
- 更新于 : 2025-08-08 09:29:22
- 链接: https://blog.oksanye.com/9c7fd22a9f27/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。