promise要解决的问题
回调函数问题
问题一:回调函数多层嵌套调用(回调地狱)
问题二:每次回调的结果存在成功或失败的可能性
使用 promise 解决
解决问题一:promise 通过 .then 实现链式调用
解决问题二:promise 通过 .catch 统一捕获异常
Promise 的 3 种状态。
• fulfilled:表示操作已经成功完成,并准备返回结果。
• rejected:表示操作执行失败,代码可能有异常或人为地调用了 reject()。
• pending:如果状态既不是 fulfilled 也不是 rejected,则为 pending 状态,表示操作执行中。
使用 new 创建 Promise 对象之后,执行器中的代码会立即执行,此时 Promise 为 pending 状态;当调用 resolve() 函数之后,会把 Promise 的 pending 状态改为 fulfilled 状态;类似地,reject() 函数会把它从 pending 改为 rejected 状态。
fulfilled 和 rejected 状态统称为settled,可以认为是完成状态(无论是成功还是失败)。
基本语法如下:
const promise = new Promise((resolve, reject) => {
// asynchronous code goes here
});
我们首先使用Promise构造函数实例化一个新的 promise 对象,并向它传递一个回调函数。回调有两个参数,resolve和reject,它们都是函数。我们所有的异步代码都在该回调中。
如果一切运行成功,承诺将通过调用来实现resolve。如果出现错误,则会调用reject。我们可以将值传递给这两种方法,这些方法将在使用代码中可用。
Promise 提供了4种方式执行多个 Promise,分别是:
Promise.all()
Promise.allSettled()
Promise.any()
Promise.race()
接下来分别看一下它们的作用和区别。
1、Promise.all()
Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
Promise.all 等待所有兑现(或第一个拒绝)的结果。
const p1 = Promise.resolve(3);
const p2 = 1337;
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 100);
});
Promise.all([p1, p2, p3]).then((values) => {
console.log(values); // [3, 1337, "foo"]
});
2、Promise.allSettled()
方法是 promise 并发方法之一。在你有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled() 。
相比之下,如果任务相互依赖,或者如果你想在任何 promise 被拒绝时立即拒绝,Promise.all() 返回的 Promise 可能更合适。
Promise.allSettled([
Promise.resolve(33),
new Promise((resolve) => setTimeout(() => resolve(66), 0)),
99,
Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));
// [
// { status: 'fulfilled', value: 33 },
// { status: 'fulfilled', value: 66 },
// { status: 'fulfilled', value: 99 },
// { status: 'rejected', reason: Error: 一个错误 }
// ]
3、Promise.any()
当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。
该方法对于返回第一个兑现的 Promise 非常有用。一旦有一个 Promise 兑现,它就会立即返回,因此不会等待其他 Promise 完成
与 Promise.all() 返回一个兑现值数组不同的是,我们只会得到一个兑现值(假设至少有一个 Promise 被兑现)。此方法对于那些如果我们只需要一个 Promise 被兑现,但不在意哪一个被兑现的情况更有益。请注意另一个区别:该方法在接收到空的可迭代对象时会拒绝,因为实际上,该可迭代对象不包含任何兑现的项。你可以将 Promise.any() 和 Promise.all() 与 Array.prototype.some() 和 Array.prototype.every() 进行比较。
同时,与 Promise.race() 返回第一个敲定(无论是兑现还是拒绝)的值不同的是,该方法返回第一个兑现的值。该方法忽略所有被拒绝的 Promise,直到第一个被兑现的 Promise。
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value);
// pFast 第一个兑现
});
// 打印:
// 很快完成
4、Promise.race()
这个返回的 promise 会随着第一个 promise 的敲定而敲定。
当你想要第一个异步任务完成时,但不关心它的最终状态(即它既可以成功也可以失败)时,它就非常有用。
如果可迭代对象中包含一个或多个非 promise 值和/或已敲定的 promise,则 Promise.race() 将以可迭代对象中找到的第一个此类值敲定。
function sleep(time, value, state) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (state === "兑现") {
return resolve(value);
} else {
return reject(new Error(value));
}
}, time);
});
}
const p1 = sleep(500, "一", "兑现");
const p2 = sleep(100, "二", "兑现");
Promise.race([p1, p2]).then((value) => {
console.log(value); // “二”
});
const p3 = sleep(100, "三", "兑现");
const p4 = sleep(500, "四", "拒绝");
Promise.race([p3, p4]).then(
(value) => {
console.log(value); // “三”
// p3 更快,所以它兑现
},
(error) => {
// 不会被调用
},
);
const p5 = sleep(500, "五", "兑现");
const p6 = sleep(100, "六", "拒绝");
Promise.race([p5, p6]).then(
(value) => {
// 不会被调用
},
(error) => {
console.error(error.message); // “六”
// p6 更快,所以它拒绝
},
);
接下来我们来看两个经常面试会遇到的问题:
1、来用primise实现promise.all:
let pall = function(...args){
let Res = []
let Err = []
return new Promise((resolve,reject)=>{
for(let i=0;i<args.length;i++){
args[i].then(res=>{
Res.push(res)
if(Res.length == args.length){
resolve(Res)
}
}).catch(err=>{
Err.push(err)
reject(Err)
})
}
})
}
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("p1")
},1000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("p2")
},500)
})
pall(p1,p2).then(res=>{
console.log(res)
}).catch(err=>console.log(err))
2、Promise缓存
在大型项目中诸多公用的方法和能力,比如获取用户信息,获取某场景配置信息等。在我们进行搭建页面时,往往会存在多模块调用同一个公用方法的情况,比如这个模块需要进行用户信息展示,那个模块需要判断用户是否符合活动人群。如果全局没做状态管理的话,每调用一次,就会发起一次请求。在大流量的场景下,不但会提升QPS,可能触发服务器调用阀值,影响用户体验,同时也很浪费服务器资源,这个时候我们就可以去做一个Promise的缓存。
QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
互联网中,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。
promise缓存的模拟简化代码:
let times = 1;
let promiseCache = null; // 此处值固定,如果有个key/value值,可以使用map
const mockRequest = ()=>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log('执行第'+times+'次');
let userId = '123';
times++;
resolve(userId);
}, 3000)
})
}
const mockRequestMemoize = ()=>{
if (!promiseCache) {
const userPromise = mockRequest();
promiseCache = userPromise; // 获取了用户信息之后将promise结果记录在缓存中
}
return promiseCache;
}
const doMoreRequestMemoize = () => { //模拟执行多次
mockRequestMemoize().then(res=>{
console.log('res1:', res)
})
mockRequestMemoize().then(res=>{
console.log('res2:', res)
})
setTimeout(()=>{
mockRequestMemoize().then(res=>{
console.log('res3:', res)
})
}, 2000)
}
doMoreRequestMemoize();
结果:
执行第1次
res1: 123
res2: 123
res3: 123
为什么是缓存promise,而不是缓存结果?
如果是缓存结果,那么需要第一个调用拿到请求结果后,才会记录缓存,但当此时,再来第二个请求,将会再次发起请求。
无论时间如何,当我们进行多次调用时,只会触发一个网络请求。这是因为所有后续调用者都收到与第一个相同的 Promise 单例。