本专栏聚焦Promise的核心原理与高级应用,包含: ✓ Promise A+规范深度解读 ✓ 手写实现与源码分析 ✓ 异步编程设计模式 ✓ 性能调优与错误处理
适合有JavaScript基础,希望深入异步编程的开发者。我们将用最少的篇幅,讲透最核心的知识。
引言:Promise静态方法
在之前的文章中,我们探讨了Promise的状态机制和实例方法。本篇文章将介绍Promise的静态方法,即:直接通过Promise类调用的方法。我将这些方法分成了两个大类,一类是创建类方法,一类是控制类方法:
-
创建类方法:创建Promise的多种方式,主要分为三种:
Promise构造函数、Promise.resolve()、Promise.reject()。 -
控制类方法:本质是管理控制多个Promise,并使其能协调工作,主要包括:
Promise.all()、Promise.race()、Promise.allSettled()、Promise.any()。
创建类方法
Promise构造函数
Promise构造函数的基本语法
Promise构造函数是创建Promise对象的根本方法,它将回调的异步操作包装为Promise,其基本语法如下:
javascript
const myPromise = new Promise((resolve, reject) => {})
上述代码创建了一个空的Promise对象,我们在创建Promise时,也可以进行一些初始化操作,如以下代码所示:
javascript
const myPromise = new Promise((resolve, reject) => {
// 这个函数会立即执行
console.log('executor开始执行');
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('操作成功');
} else {
reject(new Error('操作失败'));
}
}, 1000);
console.log('executor执行完毕');
});
console.log('Promise已创建,状态:', myPromise);
这段代码的执行结果是:
bash
executor开始执行
executor执行完毕
Promise已创建,状态: Promise { <pending> }
从结果中可以看出,此时的Promise状态一直为pending 。
构造函数的三大特性
立即执行
Promise构造函数中的同步代码会立即执行,而异步代码不会执行。
javascript
console.log('开始');
const myPromise = new Promise((resolve, reject) => {
console.log('executor执行中...');
resolve('完成');
});
console.log('结束');
上述代码的执行结果是:
javascript
开始
executor执行中...
结束
resolve/reject只能调用一次
由于Promise的状态机制,Promise中的resolve/reject只能调用一次,多个resolve/reject会被自动忽略,不会执行:
javascript
const myPromise = new Promise((resolve, reject) => {
resolve('第一次调用有效');
resolve('第二次调用被忽略'); // 无效
reject(new Error('这个也被忽略')); // 无效
});
异常自动转换为reject
javascript
const p = new Promise((resolve, reject) => {
throw new Error('执行器异常'); // 自动调用reject
// 等同于:reject(new Error('执行器异常'))
});
Promise.resolve()
Promise.resolve() 方法用于创建一个状态为 fulfilled 的Promise对象,它其实和 new Promise((resolve, reject) => resolve()) 这段代码等价。使用这个方法,实际上可以把任意值都转成一个Promise,有4种处理模式:
包装普通值
javascript
const p1 = Promise.resolve('hello'); // Promise<fulfilled: 'hello'>
当有多个值的时候,Promise会怎么处理呢?
javascript
const p2 = Promise.resolve('hello', 'world'); // Promise<fulfilled: 'hello'>
在这种情况下,Promise.resolve()只会处理第一个参数,而把后面的参数全部忽略掉,即上述两段代码中p1和p2是等价的:p1 === p2 // true 。
包装thenable对象
javascript
const thenable = {
then: function(onFulfilled, onRejected) {
setTimeout(() => onFulfilled('thenable的结果'), 100);
}
};
const p = Promise.resolve(thenable);
包装异步值
javascript
async function asyncFunc() {
return 'async返回值';
}
const p = Promise.resolve(asyncFunc());
包装Promise对象
javascript
const originalPromise = new Promise(resolve =>
setTimeout(() => resolve('原始值'), 200)
);
const p = Promise.resolve(originalPromise);
console.log(p === originalPromise); // true,是同一个对象
对于这段代码,我们可以看到,p 和 originalPromise 居然也是等价,这又是为什么呢? 原来,当 Promise.resolve() 方法中,接收到一个Promise参数时,那它的行为就类似一个空包装,因此我们也可以是 Promise.resolve() 方法是一个幂等方法,即:
javascript
const p = Promise.resolve(1);
console.log(p === Promise.resolve(Promise.resolve(Promise.resolve(p))));
上述代码里面无论包多少层Promise.resolve() 方法,其结果永远为true。
Promise.reject()
这个方法和 Promise.resolve() 方法对应,用于创建一个 rejected 状态的Promise实例,并抛出一个异步错误。
注:
Promise.resolve()方法抛出异步错误时,是无法使用try/catch进行捕获的。
Promise.reject() 方法和 Promise.resolve() 方法基本类似,本文不再赘述,唯一要注意的点是:Promise.reject() 方法并没有照搬 Promise.resolve() 方法的幂等逻辑,如果给Promise.reject() 方法传递一个Promise参数,则这个Promise参数会成为它返回的拒绝原因。
控制类方法
所谓控制类方法,其实就是Promise提供的将多个Promise实例,组合成一个Promise实例的静态方法,合成后的Promise行为取决于内部约定的行为。
Promise.all()
Promise.all() 方法创建的Promise,会在所有Promise全部解决后,再解决,即:只要中间有一个Promise状态为 rejected ,则最终结果为rejected 。当有多个Promise状态都为 rejected 时,则第一个状态为 rejected 的Promise实例,会将自己的理由作为最终的拒绝理由。我们来看看下面一个例子:
javascript
const fastReject = new Promise((resolve, reject) => {
setTimeout(() => reject("快速失败"), 10);
});
const slowReject = new Promise((resolve, reject) => {
setTimeout(() => reject("慢速失败"), 100);
});
const fastResolve = new Promise((resolve) => {
setTimeout(() => resolve("快速成功"), 50);
});
Promise.all([fastReject, slowReject, fastResolve])
.then((results) => {
console.log("所有 Promise 都成功了:", results);
})
.catch((error) => {
console.log("Promise.all() 失败:", error);
});
上述代码输出结果是:Promise.all() 失败: 快速失败 。
Promise.allSettled()
Promise.allSettled() 是ES2020新增的方法,它等待所有Promise完成(无论成功或失败),然后报告每个Promise的结果。相比于 Promise.all() 方法,Promise.allSettled() 更像一个记录员,记录每个Promise的结果。我们来看看下面一个例子:
javascript
// 创建三个 Promise
const p1 = Promise.resolve("成功1");
const p2 = Promise.reject("失败2");
const p3 = new Promise((resolve) => {
setTimeout(() => resolve("成功3"), 1000);
});
// 使用 Promise.allSettled()
Promise.allSettled([p1, p2, p3])
.then((results) => {
console.log("所有 Promise 都已解决:");
results.forEach((result, index) => {
console.log(`p${index + 1}:`, result);
});
});
上述代码的输出结果是:
bash
所有 Promise 都已解决:
p1: { status: 'fulfilled', value: '成功1' }
p2: { status: 'rejected', reason: '失败2' }
p3: { status: 'fulfilled', value: '成功3' }
Promise.race()
Promise.race() 返回最先完成的Promise(无论成功还是失败)的结果。我们来看看下面一个例子:
javascript
// 创建一个可能会长时间运行的 Promise
function longTask(duration = 3000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`长时间任务完成(耗时 ${duration}ms)`);
}, duration);
});
}
// 创建一个超时 Promise
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(`操作超时(${ms}ms)`);
}, ms);
});
}
Promise.race([longTask(3000), timeout(2000)])
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.log("失败:", error);
});
上述代码的执行结果是:失败: 操作超时(2000ms) 。
Promise.any()
Promise.any() 是ES2021新增的方法,它返回第一个成功的Promise,只有当所有Promise都失败时才失败。我们来看看下面一个例子:
javascript
const successFast = new Promise((resolve) => {
setTimeout(() => resolve("快速成功"), 100);
});
const successSlow = new Promise((resolve) => {
setTimeout(() => resolve("慢速成功"), 1000);
});
const failure = new Promise((_, reject) => {
setTimeout(() => reject("失败"), 50);
});
// 即使有失败的 Promise,只要有一个成功就行
Promise.any([failure, successFast, successSlow])
.then((result) => {
console.log("成功:", result);
})
.catch((error) => {
console.log("全部失败:", error);
});
上述代码的输出结果是:成功: 快速成功 。
结语
本文主要介绍了Promise的几种静态方法,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!