JavaScript基础提升【六】
JavaScript中的Promise解决了异步编程中的回调地狱问题,提供了更清晰的处理异步操作的方式。当函数执行需要延迟时(如setTimeout),传统回调方法会使代码难以维护。Promise通过状态(Pending/Fulfilled/Rejected)管理异步结果,支持链式调用(then/catch/finally)。ES6还引入了Promise.all(聚合多个Promise)、Prom
目录
JavaScript中的Promise
为什么要使用Promise
假设有一个函数getUsers用来获取一个用户对象列表,同时有一个函数findUser要从getUsers返回的用户列表中找到指定用户,那么这串代码可能是这样的:
function getUsers() {
return [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
console.log(findUser('john')); // { username: 'john', email: '[email protected]' }
这串代码是同步代码,代码的执行是有顺序的:
- findUser()
- getUsers()
- console.log(...)
各个函数的调用并没有延迟,可是如果某个函数的调用需要延迟几秒,那么此时该如何呢?
function getUsers() {
let users = [];
setTimeout(() => {
users = [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}, 1000)
return users;
}
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
console.log(findUser('john')); // undefined
因为users的返回需要1s时间,但是javascript的同步代码执行并不会等待这个1s,所以:“const users = getUsers()”这个代码相当于“const users”,故返回undefined
你可能想到使用回调函数来解决这个问题:
function getUsers(callback) {
setTimeout(() => {
callback([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
})
}
function findUser(username, callback) {
getUsers((users) => {
const user = users.find((user) => user.username);
callback(user);
})
}
findUser('john', console.log); // { username: 'john', email: '[email protected]' }
回调方法非常有效,但它使代码更难理解了。此外,如果函数数量增加,会遇到回调地狱的问题,为了解决这个问题,JavaScript提出Promise。
Promise简介
Promise是一个对象,它封装了“异步操作”的结果。
Promise对象必须有一个状态,可以是下面三个状态之一:
- 等待中(Pending)
- 已完成,带有值(Fulfilled)
- 已拒绝,带有原因(Rejected)
一个Promise定义后,就是Pending状态,表示异步操作正在进行,根据异步操作的结果,状态将变为已完成或已拒绝。
创建Promise
使用Promise()构造函数创建Promise对象,Promise构造函数接受一个回调函数,该函数通常执行异步操作。
回调函数接受两个回调函数,分别名为“resolve”和“reject”,如果异步操作成功应手动调用resolve函数,将Promise状态从Pending更改为Fulfilled,反之变为Rejected
Promise达到“Fulfilled”或“Rejected”之后,它将保持该状态,无法再进入其他状态
使用then、catch、finally
then()
获取Promise在Fulfilled状态下的值,可以调用then()方法,语法:
promise.then(onFulfilled, onRejected)
- onFulfilled:如果Promise已完成,调用该函数
- onRejected:如果Promise已拒绝,调用该函数
onFulfilled和Onrejected都是可选的
catch()
如果只想在Promise状态为拒绝时获取错误,可以使用catch()方法,语法:
promise.catch(onRejected)
finally()
有时,不管Promise成功或拒绝都想执行一段代码,可使用finally(),语法:
promise.finally()
- finally没有参数
Promise链
在ES7发布“await/async”后,不推荐使用Promise链,语法冗余且不易维护。
Promise的then方法支持return语法,不管return什么,默认都是一个新的Promise,所以如果你想的话可以这样做:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 1000);
})
const a = p.then((result) => {
console.log("result1", result);
return result * 2;
})
console.log("a", a);
setTimeout(() => {
console.log("a", a);
}, 2000);
结果:

你可能还会想到,那我是不是可以对“a”这个新Promise进行一个then操作?当然可以:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 1000);
})
const a = p.then((result) => {
console.log("result1", result);
return result * 2;
})
a.then((result) => {
console.log("result2", result);
return result * 4;
})
如此then()方法不断返回新Promise,新Promise再不断调用then()方法,叫做Promise链。
一个Promise的多个处理程序
在同一个Promise上多次调用then()方法时,这不是Promise链:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3 * 100);
});
p.then((result) => {
console.log(result); // 10
return result * 2;
})
p.then((result) => {
console.log(result); // 10
return result * 3;
})
p.then((result) => {
console.log(result); // 10
return result * 4;
});
结果:

Promise.all()
Promise.all()接受一个包含“可迭代对象”的Promise
Promise.all(iterable);
Promise.all()方法返回一个单一Promise,当所有Promise都已解析时,该Promise才会解析,返回的Promise解析结果是一个数组。

在此图中,promise1在t1时解析为值v1,promise2在t2时解析为值v2。因此,Promise.aPll(promise1, promise2)返回一个在t2时解析为一个数组的Promise,即[v1, v2]
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise1');
resolve(10);
}, 1 * 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise2');
resolve(20);
}, 2 * 1000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise3');
resolve(30);
}, 3 * 1000);
});
Promise.all([p1, p2, p3]).then((results) => {
const total = results.reduce((p, c) => p + c);
console.log(`结果: ${results}`);
console.log(`总共: ${total}`);
});
结果:

如果其中一个Promise被拒绝,则Promise.all()方法立即返回一个被拒绝的Promise

在此图中,promise2在t1时使用error拒绝,因此Promise.all()放回一个新的Promise,该Promise会立即使用相同的错误拒绝。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise1');
resolve(10);
}, 1 * 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise2 rejected');
reject('Rejected caused kill');
}, 2 * 1000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise3');
resolve(30);
}, 3 * 1000);
});
Promise.all([p1, p2, p3]).then((results) => {
const total = results.reduce((p, c) => p + c);
console.log(`结果: ${results}`);
console.log(`总共: ${total}`);
});
结果:

总之,Promise.all()有助于聚合多个异步操作的结果
Promise.race()
Promise.race()接受一个包含promise的可迭代对象,并返回一个新的promise。该promise一旦其中一个promise fulfilled或reject,就会立即fulfilled或reject
Promise.race(iterable)

在该图中:
- promise1在t1时用值v1 fulfilled
- promise2在t2时用error reject
- 由于promise1比promise2提前完成,因此Promise.race()返回的Promise状态是fulfiiled
Promise.any()
Promise.any()接受一个Promise对象列表作为可迭代对象。

在该图中:
- promise1在t1时解析为值v1
- promise2在t2时解析为值v2
- Promise.any()返回一个Promise,该Promise在t1时为值v1,即promise1的结果
如果可迭代对象中所有Promise都失败,或者可迭代对象为空,则Promise.any()返回一个Promise,该Promise被一个包含所有拒绝原因的AggregateError拒绝。

在该图中:
- promise1在t1时被error1拒绝
- promise2在t2时被error2拒绝
- Promise.any()返回一个Promise,该Promise在t2时被拒绝,并包含一个AggregateError,其中包含所有被拒绝的Promise的error1和error2
Promise.allSettled()
ES2020引入了Promise.allSettled()方法,它接受一个Promise列表,并返回一个新promise,该promise在所有输入promise结算后解析,无论是已解析还是已拒绝。
Promise.allSettled(iterable);
allSettled()返回的promise解析为一个数组,其中包含对象,每个对象描述输入的promise结果:
每个对象都有两个属性:status和value
- status:可以是fulfilled或rejected
- value:resolved或reject的原因

在本图中:
- promise1在t1时拒绝error
- promise2在t2时解析为value
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('The first promise has resolved');
resolve(10);
}, 1 * 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('The second promise has rejected');
reject(20);
}, 2 * 1000);
});
Promise.allSettled([p1, p2])
.then((result) => {
console.log(result);
});
结果:

Promise.finally()
finally()在Promise状态解决时执行
promise.finally(onFinally)
- onFinally:一个函数,当Promise解决时异步执行
finally()方法类似于try...catch...finally语句中的finally块
promise
.then(result => {
// process the result
})
.catch(error => {
// handle the error
})
.finally(() => {
// clean up the resources
});
async/await
async/await是Promise的语法糖,Promise的提出用来解决“回调地狱”问题,而async/await的提出用来解决“Promise链”问题。
它可以用来编写更像同步代码且更具可读性的异步代码。
async关键字
async关键字允许您定义一个处理异步操作的函数
要定义一个async函数,需要在函数关键字前添加async关键字:
async function sayHi() {
return 'Hi';
}
async函数中返回一个Promise
await关键字
使用await关键字来等待一个Promise解决,无论是处于已解决状态还是拒绝状态,只能在async函数内使用await关键字
Promise.withResolvers
当创建一个新的Promise对象时,通常会将resolve和reject函数传递给Promise构造函数:
const promise = new Promise((resolve, reject) =>{
// ...
});
这仅仅允许在Promise内部调用resolve和reject,聪明的你可能想到创建外部变量来存储resolve和reject从而实现在Promise外部调用resolve和reject:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
Math.random() > 0.5 ? resolve("Success") : reject("Error");
使用Promise.withResolvers()函数,可以像这样简化代码:
const { promise, resolve, reject} = Promise.withResolvers();
Math.random() > 0.5 ? resolve("Success") : reject("Error");
- promise:一个新的Promise对象
- resolve:用于解决Promise的函数
- reject:用于拒绝Promise的函数
PS:Promise.withResolvers()函数在ES2024后才可以使用
其它
更多JavaScript学习可以参考我的专栏:
更多推荐


所有评论(0)