0%

Promise详解

1. 含义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

2. 异步和回调

只要有一件异步事情发生时,就会存在两个阶段 unsettled未决阶段和settled已决阶段
事情总是从未决阶段走向已决阶段,并且未决阶段拥有控制通往已决阶段的能力,可以决定事情最终走向的结果。

将程序分为三种状态 pending resolved rejected。
pending 等待 处于unsettled阶段,表示事情还在等待最终的结果。
resolved 已处理 处于setteled阶段,表示事情已经出现结果,并且可以按照正常的逻辑进行下去的结果。
rejected 已拒绝 处于setteled阶段,表示事情已经出现结果,并且不可以按照正常的逻辑进行下去的结果。

把事情从pending状态推向resolved状态的过程中,可能会传递一些数据,这些数据为真实有效数据。
把事情从pending状态推向rejected状态的过程中,可能会传递一些数据,这些数据为错误信息

无论是在哪个阶段还是那个状态,都是不可逆。

当事情已经到达已决阶段后,通常用结果数据做一些后续处理,不同的已决结果,可能造成不同的后续处理。
resolved 后续处理表示为thenable
rejected 后续处理表示为catchable

后续处理可能有多个,因此会形成任务队列,这些后续处理会按照顺序当到达对应的状态时会依次执行。

3. promise方法

方法 类型 简介
then() 原型方法 可以处理thenable和catchable
catch() 原型方法 可以处理catchable
finally() 原型方法 用于不管promise最后状态如何,都会执行finally方法
resolve() 静态方法 创建已解决的 Promise
reject() 静态方法 创建已拒绝的 Promise
all() 静态方法 等待所有 Promise 完成
allSettled() 静态方法 等待所有 Promise 完成(无论成功失败)
race() 静态方法 第一个完成的 Promise(无论成功失败)
any() 静态方法 第一个成功的 Promise
Promise.all()
1
2
3
4
5
6
7
8
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // 所有 Promise 结果的数组
})
.catch(error => {
// 任一 Promise 被拒绝
console.error(error);
});
Promise.allSettled()
1
2
3
4
5
6
7
8
9
10
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
Promise.race()
1
2
3
4
5
6
7
8
9
Promise.race([promise1, promise2])
.then(value => {
// 第一个成功的 Promise
console.log(value);
})
.catch(error => {
// 第一个失败的 Promise
console.error(error);
});
Promise.any()
1
2
3
4
5
6
7
8
9
Promise.any([promise1, promise2])
.then(value => {
// 第一个成功的 Promise
console.log(value);
})
.catch(errors => {
// 所有 Promise 都失败
console.error(errors);
});

4. promise解决了什么问题

  1. 回调地狱问题:
    Promise是回调地狱的解决方案之一,我们使用Promise的语法来解决回调地狱的问题,使代码拥有可读性和可维护性,有时候前端为了能够拿到异步的数据,使用了大量的回调函数,来获取将来异步执行成功之后的数据。从一定程度上来说,回调地狱能解决问题,但是有缺点,或者说不优雅,阅读性非常差,而Promise就解决了这个问题。

  2. 代码的可读性问题:
    让代码更加直观优雅精简,让人更容易的去阅读代码,也方便代码bug的寻找。

  3. 信任问题:
    回调函数不能保证什么时候去调用回调,以及使用什么方式去调用回调;而Promise一旦被确认成功或失败,就不能再被更改。Promise成功之后仅调用一次resolve(),不会产生回调多次执行的问题。除非Promise再次调用。所以Promise很好地解决了第三方工具导致的回调多次执行(控制反转)的问题。

5. promise有哪几个状态

  1. pending等待
  2. resolved已处理
  3. rejected已拒绝

6. Promise.withResolvers

Promise.withResolvers 的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。

它解决了三大痛点:

  1. 告别变量泄露:不再需要外部变量保存 resolve/reject
  2. 代码更聚焦:将创建 Promise 和逻辑控制解耦
  3. 灵活度翻倍:支持跨模块、跨函数控制异步状态
1. 与传统写法的直观对比

传统方式

1
2
3
4
let manualResolve;  // 需要外部变量
const oldPromise = new Promise((resolve) => {
manualResolve = resolve
})

新写法

1
2
const { promise, resolve } = Promise.withResolvers()
// 随时在任意位置调用 resolve()

相当于浏览器内置了变量保存机制

2. 底层实现揭秘
1
2
3
4
5
6
7
8
function withResolvers() {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}

但原生实现避免了作用域污染问题

3. 三大核心特性
  • 即用即取:解构后直接使用 resolve/reject
  • 跨域控制:可在不同函数间传递控制器
  • 零副作用:不会影响其他 Promise 实例
实战场景解析
1. 事件监听器改造(比传统简洁50%)
1
2
3
4
5
6
7
8
9
10
11
// 传统方式需要嵌套在Promise构造函数中
function waitForClick(element) {
const { promise, resolve } = Promise.withResolvers()
element.addEventListener('click', resolve, { once: true })
return promise
}

// 使用案例
const button = document.querySelector('#submit')
const clickPromise = waitForClick(button)
clickPromise.then(() => console.log('按钮被点击!'))
2. 可取消的异步操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function cancellableFetch(url) {
const { promise, resolve, reject } = Promise.withResolvers()
const controller = new AbortController()

fetch(url, { signal: controller.signal })
.then(resolve)
.catch(reject)

return {
promise,
cancel: () => {
controller.abort()
reject('用户取消请求')
}
}
}

// 使用示例
const { promise, cancel } = cancellableFetch('https://api.example.com')
setTimeout(cancel, 5000) // 5秒后自动取消
3. 多步骤异步队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class AsyncQueue {
constructor() {
this.queue = []
this.current = Promise.withResolvers()
}

add(task) {
this.queue.push(task)
if(this.queue.length === 1) this.runNext()
}

runNext() {
if(!this.queue.length) return

const task = this.queue.shift()
task().then(result => {
this.current.resolve(result)
this.current = Promise.withResolvers()
this.runNext()
})
}
}
4. 跨组件通信桥梁
1
2
3
4
5
6
7
8
9
10
11
// 父组件
const modalController = Promise.withResolvers()
<Modal :promise="modalController.promise" />

// 子组件
props: ['promise'],
methods: {
confirm() {
this.promise.resolve('用户确认')
}
}
  1. 与传统写法的核心区别是什么?
    答:解决变量作用域泄露问题,提供标准化控制接口
  2. 如何实现请求重试机制?
1
2
3
4
5
6
7
8
9
10
function retry(fn, times) {
const attempt = (n) => {
const { promise, resolve, reject } = Promise.withResolvers()
fn().then(resolve).catch(err => {
n > 0 ? attempt(n-1) : reject(err)
})
return promise
}
return attempt(times)
}
  1. 在Web Worker中如何使用?
    答:主线程与 Worker 通过 postMessage 传递 resolve/reject 控制权
  2. 与Observable的区别?
    答:Observable处理数据流,本方案专注单次异步操作控制
-------------本文结束感谢您的阅读-------------