Promise 对象
基本概念
定义
JavaScript ES6中引入的 Promise 是一种用于处理异步操作的对象(一个构造函数)。Promise 代表了一个异步操作的最终完成(或失败)及其结果值。它使得异步操作的处理更加直观,尤其是比传统的回调函数更加易于管理。
Promise 有三种状态:
- Pending(进行中):初始状态,表示异步操作正在进行中。
- Fulfilled(已完成):表示异步操作成功完成。
- Rejected(已拒绝):表示异步操作失败。
Promise 可以帮助解决回调地狱问题,使代码更易读、更可维护。
创建 Promise
Promise
的构造函数接受一个执行函数,该执行函数有两个参数:resolve
和 reject
。resolve
用于将 Promise
状态从 Pending 转换为 Fulfilled,reject
用于将状态转换为 Rejected。
1 | let promise = new Promise((resolve, reject) => { |
reject()
是 Promise的方法:Promise的静态方法 || 构造函数内部的一个参数
.then()
和.catch()
是 Promise 实例的方法(Promise.prototype.then
和Promise.prototype.catch
)
状态转换
创建 Promise 时,此 Promise 状态为 pending,他的 result 值为 undefined
当调用 resolve() 或 reject() 时,Promise 的状态发生改变
resolve(‘成功’) 此时 Promise 状态为 fulfilled,result 值为 resolve 返回值就是’成功’
reject(‘失败’) 此时 Promise 状态为 rejected,result 值为 reject 返回值就是’失败’
状态转换的规则:
- 从 Pending 状态转变为 Fulfilled 状态时,
Promise
将调用.then()
中的成功回调函数。 - 从 Pending 状态转变为 Rejected 状态时,
Promise
将调用.catch()
中的失败回调函数。
当 Promise 的状态发生改变之后,promise状态不会再发生改变。
基本方法
.then()
方法
当 promise 状态发生改变的时候,就会执行
.then()
方法。相反状态没有发生改变的时候.then()
就不会执行
基本语法:.then((value) => {console.log(value)}, (error) => {console.log(error)})
用于处理
Promise
变为 Fulfilled 的情况,并且它会**返回一个新的Promise
**。传两个参数,分别是两个回调函数,value值是成功、失败的结果值。
- 第一个是 promise 状态变为
fulfilled
时执行的回调函数 - 第二个时状态变为
rejected
时执行的回调函数
1
2
3
4
5
6
7
8
9
10
11
12const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('成功')
// reject('失败')
})
myPromise.then(
(value) => {
console.log('value', value);
},
(error) => {
console.log(error);
})- 第一个是 promise 状态变为
这些方法支持链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('第一个成功数据')
})
myPromise.then((data) => {
console.log('第一次成功:', data);
return '这是第二个成功数据';
})
.then((data) => {
console.log('第二次成功:', data);
})
.catch((error) => {
console.error('失败:', error);
});注意:
.then()
里面的回调 return 一个值的时候,这个值不是 promise 对象,就会自动将这个值包裹成 promise 对象。【支持链式调用】- 例:
return '成功' => return Promise.resolve('成功')
- 例:
.then()
和.catch()
的 return 亦是如此。
.catch()
方法
基本语法:.catch((error) => {console.log('error',error)})
用于处理
Promise
变为 Rejected 的情况,并且它会返回一个新的Promise
。- 通常在
.then()
的第二个参数中传递失败回调函数也可以,但.catch()
更常用于处理错误。
- 通常在
作用是,可以捕获上层未能捕获到的错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
reject('失败')
})
myPromise.then((data) => {
console.log('第一次成功:', data);
return '这是第二个成功数据';
})
.then((data) => {
console.log('第二次成功:', data);
})
.catch((error) => {
console.error('失败:', error);
});
/** output:
* promise实例
* 失败: 失败
*/- 前两次
.then()
时,没有传递第二个参数,所以未捕获错误,在.catch()
的时候,捕获到错误。
- 前两次
.then()
中抛出错误,.catch()
中捕获1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const myPromise = new Promise((resolve, reject) => {
console.log('promise实例');
resolve('成功')
})
myPromise.then((data) => {
console.log('第一次成功:', data);
throw new Error('error in then')
// return Promise.reject('error in then') // 作用同上
// return new Error('sucess error in then'); // 返回一个 Error 对象会将其作为 Promise 的成功结果处理
})
.then((data) => {
console.log('第二次成功:', data);
})
.catch((error) => {
console.error('失败:', error);
});
/** output:
* promise实例
* 第一次成功: 成功
* 失败: Error: error in then
at /box/script.js:7:11
at processTicksAndRejections (internal/process/task_queues.js:93:5)
*/- 注意三种方式的区别:
return new Error('error')
:返回一个Error
对象会将其作为Promise
的成功结果处理,而不是错误。后续的catch
不会捕获这个Error
,因为Promise
被解析为一个正常的Error
对象。throw new Error('error')
:显式抛出异常会将Promise
状态改为拒绝(rejected),catch
会捕获到这个异常并处理它。后续的catch
函数会被调用。return Promise.reject('error')
:返回一个被拒绝的Promise
,也会使当前Promise
进入拒绝状态。后续的catch
函数同样会捕获到这个错误。
- 注意三种方式的区别:
.finally()
方法
.finally()
方法,无需传递参数,只要 promise 状态改变的时候,就会执行,无论状态变为 fulfilled 还是 rejected
基本语法:.finally(() => {console.log('finally operation')})
.finally()
用于在Promise
完成时执行一些清理操作,不论是成功还是失败。1
2
3
4promise
.then(result => { /* 处理成功结果 */ })
.catch(error => { /* 处理错误 */ })
.finally(() => { /* 执行清理操作 */ });
.then()
的第二个参数 和 .catch()
区别
在Promise当中抛出的错误,.then()
的第二个参数和 .catch()
捕获的时候会遵循就近原则。
主要区别:如果在 .then()
的第一个函数里抛出了异常,后面的 .catch()
能捕获到,而 .then()
的第二个函数捕获不到
.catch()
只是一个语法糖,本质还是通过.then()
来处理的1
2
3Promise.prototype.catch = function(fn){
return this.then(null,fn);
}
举例
throw
会在 Promise
的构造函数内部直接抛出错误,Promise
会立即进入拒绝状态(rejected)。这个错误对象会被捕获。
1 | const promise = new Promise((resolve, reject) => { |
此时只有then的第二个参数可以捕获到错误信息
1
2
3
4
5
6
7promise.then(res => {
//
}, err => {
console.log('then2catch', err); // 【捕获】Promise error
}).catch(err1 => {
console.log('catch', err1); // 【不会捕获】
});此时catch方法可以捕获到错误信息
1
2
3
4
5promise.then(res => {
//
}).catch(err1 => {
console.log('catch', err1); // 【捕获】Promise error
});此时只有then的第二个参数可以捕获到 Promise 内部抛出的错误信息
1
2
3
4
5
6
7promise.then(res => {
throw new Error('then1 error');
}, err => {
console.log('then2catch', err); // 【捕获】Promise error
}).catch(err1 => {
console.log('catch', err1); // 【不会捕获】由于并没有进入到then的第一个参数,也就没有抛出错误。
});此时then的第二个参数可以捕获到 Promise 内部抛出的错误信息; catch方法可以捕获到 then的第二个参数 抛出的错误
1
2
3
4
5
6
7
8promise.then(res => {
throw new Error('then1 error');
}, err => {
console.log('then2catch', err); // 【捕获】Promise error
throw new Error('then2 error'); // 并抛出错误
}).catch(err1 => {
console.log('catch', err1); // 【捕获】then2 error
});
总结
第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。
因此,建议总是使用catch方法,而不使用then方法的第二个参数。使得异步代码更易于编写、理解和维护
简单示例
1 | const fetchData = new Promise((resolve, reject) => { |
Promise 相关的静态方法
Promise.all(iterable)
接受一个可迭代对象(如数组),并返回一个
Promise
,该Promise
会在所有输入的Promise
都成功时成功,或者在任何一个输入的Promise
失败时失败。实际上,
Promise.all()
只接受Promise
对象的可迭代对象作为输入。它的设计是用来处理多个Promise
对象,并等待它们全部解决或拒绝。如果传入的数组中包含非
Promise
对象(如数字、字符串等),Promise.all()
会将这些非Promise
值视为已解决的Promise
。这意味着它们会立即被视为解决状态,并且Promise.all()
将继续处理这些值。
1
2
3Promise.all([promise1, promise2])
.then(results => { /* 所有 Promise 成功 */ })
.catch(error => { /* 任何 Promise 失败 */ });在如下示例中,
Promise.all()
会等待promise1
、promise2
和promise3
都解决,然后返回一个包含所有结果的数组。如果将
promise1
和promise2
改为reject
则results
为 【Error: Result from promise 2】(因为最先失败的是promise2)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Result from promise 1'), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Result from promise 2'), 500);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Result from promise 3'), 1500);
});
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // 输出 ['Result from promise 1', 'Result from promise 2', 'Result from promise 3']
})
.catch(error => {
console.error('Error:', error);
});Promise.race(iterable)
接受一个可迭代对象,并返回一个
Promise
,该Promise
会在第一个输入的Promise
完成时完成,不论是成功还是失败。其他异步任务仍然执行,但是结果不保留(不会放在成功的回调里)1
2
3Promise.race([promise1, promise2])
.then(result => { /* 第一个 Promise 完成 */ })
.catch(error => { /* 第一个 Promise 失败 */ });在如下示例中,
promise2
完成得更快,所以Promise.race()
返回的 Promise 将以 ‘Promise 2 resolved’ 解决,而 promise1 会继续执行,但结果不会影响最终的 Promise 状态。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const promise1 = new Promise((resolve, reject) => {
setTimeout(() => { console.log('Promise 1'); resolve('Promise 1 resolved'); }, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => { console.log('Promise 2'); resolve('Promise 2 resolved'); }, 500);
});
Promise.race([promise1, promise2])
.then(result => {
console.log(result); // 输出 'Promise 2 resolved'
});
/** output:
* Promise 2
* Promise 2 resolved
* Promise 1
*/Promise.resolve(value)
返回一个
Promise
,该Promise
以给定值value
完成。如果value
是一个Promise
,则直接返回这个Promise
。1
2Promise.resolve("Immediate result")
.then(result => { console.log(result); }); // 输出 "Immediate result"Promise.reject(reason)
返回一个
Promise
,该Promise
以给定的拒绝原因reason
被拒绝。1
2Promise.reject("Error reason")
.catch(error => { console.error(error); }); // 输出 "Error reason"
面试:Promise.all()
的手动实现
1 | function allPromisesFulfilled(p1, p2, p3, f1) { |
async
和 await
关键字【ES8 引入】
async
关键字:在函数前加上async
关键字将函数声明为异步函数。异步函数总是返回一个 Promise。如果函数返回的是一个非 Promise 的值,那么它会被自动包装成一个已解决的 Promise。await
关键字:await
只能在async
函数内部使用。它会暂停异步函数的执行,直到 Promise 解决,并且返回 Promise 的结果(内部包装的值)。如果 Promise 被拒绝(即发生错误),await
表达式会抛出该错误。
优点对比
使用 promises 和显式的 .then()
和 .catch()
1 | fetchData() |
使用 async
/ await
1 | async function fetchDataAndProcess() { |
通过使用 await
,我们可以消除使用 promises 时通常需要的明确的 .then()
和 .catch()
链。相反,我们可以以更线性和类似同步代码的方式构建代码。这使得更容易理解程序的流程并以更简洁的方式处理错误。