Promise
JS 可以定义一个Promise对象,用于在一段异步调用后通过注册的回调函数获取执行结果:
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(42);
}
else {
reject(new Error('失败'));
}
}, 1000);
});
promise.then(function (value) {
console.log('success', value); // 42
}, function (error) {
console.log('fail', error); // Error: 失败
});
Promise的构造函数接收一个函数作为参数,其具有两个参数:
-
resolve,有一个参数的函数,在执行成功后调用
-
reject,有一个参数的函数,执行失败后调用
Promise 构造函数的参数会在 Promise 对象创建后被立即调用。作为执行结构的钩子函数resolve和reject可以通过Promise.then方法注册,它可以接收2个参数,第一个是resolve,第二个是reject。
注册钩子函数更常见的方式是:
promise2.then(function (value) {
console.log('success', value); // 42
})
.catch(function (error) {
console.log('fail', error);
})
.finally(function () {
console.log('finally');
});
链式调用
Promise.then的调用结果是一个新的Promise对象,因此如果要对Promise对象结果进行多次处理,可以通过链式调用的方式:
new Promise(function (resolve, reject) {
setTimeout(() => {
let randomNum = Math.random();
console.log(`${randomNum}`);
resolve(randomNum);
}, 1000);
}).then(function (value) {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
return value + randomNum;
}).then(function (value) {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
return value + randomNum;
}).then(function (value) {
console.log(value);
});
// 0.30704685466788906
// 0.30704685466788906 + 0.6445051295238531
// 0.9515519841917421 + 0.6476301325887244
// 1.5991821167804665
这里的关键是每次.then调用后返回的是新的Promise对象,所以下面的方式是错误的:
let promise3 = new Promise(function (resolve, reject) {
setTimeout(() => {
let randomNum = Math.random();
console.log(`${randomNum}`);
resolve(randomNum);
}, 1000);
});
promise3.then(function (value) {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
return value + randomNum;
});
promise3.then(function (value) {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
return value + randomNum;
});
promise3.then(function (value) {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
return value + randomNum;
});
// 0.43215362083224207
// 0.43215362083224207 + 0.5543302400890311
// 0.43215362083224207 + 0.6470879646639638
// 0.43215362083224207 + 0.6314888642633443
如果.then返回的内容不能立即得到,需要异步执行,就不能像前面那样简单的直接return结果,而是需要return一个Promise对象:
new Promise(function (resolve, reject) {
setTimeout(() => {
let randomNum = Math.random();
console.log(`${randomNum}`);
resolve(randomNum);
}, 1000);
}).then(function (value) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
resolve(value + randomNum);
}, 1000);
});
}).then(function (value) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
let randomNum = Math.random();
console.log(`${value} + ${randomNum}`)
resolve(value + randomNum);
}, 1000);
});
}).then(function (value) {
console.log(value);
});
// 0.9100113268839283
// 0.9100113268839283 + 0.017846977573172218
// 0.9278583044571005 + 0.24503169342106612
// 1.1728899978781666
错误处理
链式调用时,可以在调用链的尾部添加一个.catch进行错误捕获,此时链上任意.then抛出错误都会被捕获:
new Promise(function (resolve, reject) {
resolve(1);
}).then(function (value) {
value++;
console.log(value);
return value;
}).then(function (value) {
value++;
console.log(value);
return value;
}).then(function (value) {
throw new Error('出错了');
}).catch(function (err) {
console.log(err);
});
// 2
// 3
// Error: 出错了
// at D:\workspace\learn-javascript\ch1\src\promise.js:135:11
// at processTicksAndRejections (node:internal/process/task_queues:96:5)
可以在.catch中重新抛出错误,重新抛出的错误将被链上的下一个.catch捕获并处理:
let promise4 = createPromise();
promise4.then(function (value){
throw new Error('出错了');
})
.catch(function (err) {
console.log(err);
throw new Error('新的错误');
})
.then(function (value) {
console.log('这里不会被执行');
})
.catch(function (err) {
console.log(err);
});
// Error: 失败
// at Timeout._onTimeout (D:\workspace\learn-javascript\ch1\src\promise.js:25:24)
// at listOnTimeout (node:internal/timers:559:17)
// at processTimers (node:internal/timers:502:7)
// Error: 新的错误
// at D:\workspace\learn-javascript\ch1\src\promise.js:151:11
API
Promise.all
如果有一组 Promise 执行,且你需要在所有 Promise 执行成功后统一处理结果,就可以使用Proise.all:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
})
]).then(function (numbers) {
console.log(numbers);
});
// [ 1, 2, 3 ]
示例中的.then中,参数numbers中包含所有 Promise 的处理结果,是一个数组,且其中的顺序与Promise.all参数中的 Promise 顺序是一一对应的。
如果这一组 Promise 中有任意要给报错,则 Promise.all 进入错误处理程序(.catch):
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Promise 失败'));
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
})
]).then(function (numbers) {
console.log(numbers);
}).catch(function (err) {
console.log(err);
});
// Error: Promise 失败
// at Timeout._onTimeout (D:\workspace\learn-javascript\ch1\src\promise.js:195:20)
// at listOnTimeout (node:internal/timers:559:17)
// at processTimers (node:internal/timers:502:7)
Promise.allSetteted
Promise.all执行时,一组 Promise 中的任意一个执行失败,都会视为整体失败,会进入异常处理流程:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('模拟异步执行失败'));
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
})
]).then(function (results) {
console.log('这里不会生效');
}).catch(function (err) {
console.log('异常捕获', err);
});
// 异常捕获 Error: 模拟异步执行失败
// at Timeout._onTimeout (D:\workspace\learn-javascript\ch1\src\promise2.js:10:24)
// at listOnTimeout (node:internal/timers:559:17)
// at processTimers (node:internal/timers:502:7)
如果你运行部分执行失败,在这种情况下依然处理正常执行后的结果,可以使用Promise.allSettled:
Promise.allSettled([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('模拟异步执行失败'));
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
})
])
.then(function (results) {
console.log('results', results);
results.forEach((result, num)=>{
if(result.status === 'fulfilled'){
console.log(`第${num}个结果成功`, result.value);
}else{
console.log(`第${num}个结果失败`, result.reason.message);
}
});
});
// 第0个结果成功 1
// 第1个结果失败 模拟异步执行失败
// 第2个结果成功 3
Promise.race
Promise.race会处理第一个执行完成的任务:
Promise.race([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('模拟异步执行失败'));
}, 3000);
})
]).then(function (result) {
console.log('第一个执行成功的任务', result);
}).catch(function (err) {
console.log('失败', err);
});
// 第一个执行成功的任务 2
如果第一个执行完成的任务是失败的,则进入错误处理程序:
Promise.race([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('模拟异步执行失败'));
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
})
])
.then(function (result) {
console.log('第一个执行成功的任务', result);
}).catch(function (err) {
console.log('失败', err);
});
// 失败 Error: 模拟异步执行失败
Promise.any
如果仅需要第一个执行成功的结果,可以使用Promise.any:
Promise.any([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('模拟异步执行失败'));
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
})
]).then(function (result) {
console.log('第一个执行成功的任务', result);
});
// 第一个执行成功的任务 1
如果所有任务都执行失败:
Promise.any([
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('任务1执行失败'));
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('任务2执行失败'));
}, 500);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('任务3执行失败'));
}, 3000);
})
])
.then(function (result) {
console.log('第一个执行成功的任务', result);
}).catch(function (errors) {
if (errors instanceof AggregateError) {
console.log('所有任务都执行失败');
for (const [index, error] of errors.errors.entries()) {
console.log(`任务${index}执行失败`, error.message);
}
}
});
// 所有任务都执行失败
// 任务0执行失败 任务1执行失败
// 任务1执行失败 任务2执行失败
// 任务2执行失败 任务3执行失败
Promise.resolve/reject
Promise.resolve可以将一个值包装成执行成功的 Promise:
Promise.resolve(1).then(function (result) {
console.log('resolve', result);
});
// resolve 1
效果相当于:
new Promise(function (resolve) {
resolve(1);
}).then(function (result) {
console.log('resolve', result);
});
// resolve 1
Promise.reject可以将一个错误包装成 Promise:
Promise.reject(new Error('模拟异步执行失败'))
.catch(function (err) {
console.log('失败', err);
});
// 失败 Error: 模拟异步执行失败
效果相当于:
new Promise(function (resolve, reject) {
reject(new Error('模拟异步执行失败'));
}).catch(function (err) {
console.log('失败', err);
});
// 失败 Error: 模拟异步执行失败
Promisefication
通常,如果需要异步执行一段任务,并在执行完毕后处理执行结果,会通过回调解决:
function asyncTask(data, callback){
setTimeout(() => {
let result = Math.random();
if(result > 0.5){
callback(new Error('模拟异步执行失败'));
}
else{
callback(null, result);
}
}, 1000);
}
asyncTask(1, (error, result)=>{
if(error){
console.log('失败', error);
}
else{
console.log('成功', result);
}
});
JS 中的回调函数通常约定第一个参数表示异常,第二个参数表示正常的返回结果。
它可以正常工作,但我们已经学过 Promise,如果能让异步任务执行返回一个 Promise 对象,代码结构会更友好:
function asyncTask(data){
return new Promise((resolve, reject) => {
setTimeout(() => {
let result = Math.random();
if(result > 0.5){
reject(new Error('模拟异步执行失败'));
}
else{
resolve(result);
}
}, 1000);
});
}
asyncTask(1)
.then(function (result) {
console.log('成功', result);
})
.catch(function (error) {
console.log('失败', error);
});
如果asyncTask是新编写的函数,这么做没什么问题,但如果是一个老旧的第三方库中的使用回调方式的函数,要让其返回一个 Promise 就需要借助工具函数进行包装和转换:
function asyncTask(data, callback){
setTimeout(() => {
let result = Math.random();
if(result > 0.5){
callback(new Error('模拟异步执行失败'));
}
else{
callback(null, result);
}
}, 1000);
}
function promisefication(asyncTask, data) {
return new Promise((resolve, reject) => {
asyncTask(data, (error, result) => {
if (error) {
reject(error);
}
else {
resolve(result);
}
});
});
}
promisefication(asyncTask, 1)
.then(function (result) {
console.log('成功', result);
})
.catch(function (error) {
console.log('失败', error);
});
工具函数可以正确转换,返回一个返回 Promise 对象的函数,但不够泛用性,只能处理接收一个参数的函数,升级为可以处理任意个参数的函数:
function asyncTask(data, callback){
setTimeout(() => {
let result = Math.random();
if(result > 0.5){
callback(new Error('模拟异步执行失败'));
}
else{
callback(null, result);
}
}, 1000);
}
function promisefication(fn){
return function (args){
return new Promise(function (resolve, reject) {
fn(args, function (err, result) {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
}
promisefication(asyncTask)(1)
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.log(err);
});
实际上通常并不需要我们自己编写这类工具函数,比如 Node.js 就提供一个:
const { promisify } = require('util');
function asyncTask(data, callback) {
setTimeout(() => {
let result = Math.random();
if (result > 0.5) {
callback(new Error('模拟异步执行失败'));
}
else {
callback(null, result);
}
}, 1000);
}
promisify(asyncTask)(1)
.then(result => {
console.log('成功', result);
})
.catch(err => {
console.log('失败', err);
});
Async
函数可以用一个async关键字修饰:
async function asyncFunc(){
return 1;
}
asyncFunc().then(function(value){
console.log(value);
});
// 1
这样的函数必然会返回一个Promise对象,如果没有显式返回,会被自动包装成 Promise对象,上面的示例等价于:
async function asyncFunc(){
return Promise.resolve(1);
}
asyncFunc().then(function(value){
console.log(value);
});
// 1
await
await等待Promise对象产生结果,并返回其结果:
let promise = new Promise(function (resolve, reject) {
setTimeout(function () {
let result = Math.random();
if (result > 0.5) {
resolve(result);
}
else {
reject(result);
}
}, 1000);
});
try {
let result = await promise;
console.log('success', result);
}
catch (e) {
console.log('fail', e);
}
程序会在await的部分暂时终止运行,以的等待Promise对象产生结果。
这种编写方式可以减少.then和.catch的编写,将异步代码变得更像是同步代码。
类方法也可以使用async和await:
class User{
constructor(name) {
this.name = name;
}
async getName(){
return this.name;
}
}
let user = new User('Mike');
let name = await user.getName();
console.log(name);
The End.
本文的完整示例可以从获取。
参考资料

文章评论