前言
在JavaScript中,Promise是一种解决异步编程问题的重要方式,下面从累积项目经验中提取Promise 的实战用法,相互学习,相互交流~
1. Promise 控制并发请求,分批处理接口
实战场景:假设有100个请求,每次控制发送5个请求,其中一个请求完毕,再加入新的请求,直到全部请求完毕。这时如果使用promsie.all,浏览器会同时发送100个请求,这样可能会造成请求阻塞、页面卡顿、甚至服务器崩溃。
// 自定义模拟接口函数
const request = params => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`执行任务编号:${params}`)
}, 1000)
}).then(res => {
console.log(res);
})
}
// 执行任务
async function promiseTask(max = 5){
const urls = Array.from({length: 100}).map((item, index) => `url${index + 1}`)
const pool = [] // 并发池
for(let i=0; i<urls.length; i++){
let url = urls[i]
let task = request(url); // 当前执行的任务
task.then((data)=>{
//每当并发池跑完一个任务,从并发池删除个任务
pool.splice(pool.indexOf(task), 1)
console.log(`${url} 结束`);
})
pool.push(task);
if(pool.length === max){
// 利用Promise.race方法来获得并发池中某任务完成的信号,
// 和await结合当有任务完成才让程序继续执行,让循环把并发池塞满
await Promise.race(pool)
}
}
}
promiseTask(5)
2. Promise 处理接口超时
实战场景:希望 Promise 在一定时间内没有的到解决就自动停止。 我们知道
Promise.race方法接收多个Promise,其中任意一个Promise存在resove或者reject,其他的就不会执行了。基于这个特点,我们可以构造代码实现给定超时时间的接口超时终止处理
// 自定义模拟请求函数的请求时间为 1000 毫秒
function request() {
return new Promise(resolve => {
setTimeout(() => {
resolve(`接口请求成功`)
}, 1000)
}).then(res => {
console.log(res);
})
}
// 通过Promise.race 实现,请求的接口和指定时间的promise 作为 Promise.race 的参数进行赛跑
const promiseTask = (request, time) => {
Promise.race([
request(),
new Promise((resolve,reject) => {
setTimeout(() => {
reject(new Error(`超时${time}毫秒`))
}, time)
}).then(res => {
console.log(res)
})
])
}
promiseTask(request, 500);
3. Promise 取消
实战场景:原生的
Promise构造不支持取消操作,在如下例子中cancellablePromise函数创建了一个可以取消的Promise。通过调用promise.cancel()方法,可以设置一个标志来拒绝Promise,从而取消它。如果尝试在取消后使用then或catch方法,将不会有任何效果,因为Promise已经被处理了
function cancellablePromise(executor) {
let isCancelled = false;
const promiseRes = new Promise((resolve, reject) => {
executor(resolve, () => {
isCancelled = true;
reject(new Error('Promise cancelled'));
});
});
// 定义一个cancel方法,用来控制
promiseRes.cancel = function() {
isCancelled = true;
};
return promiseRes;
}
const cancellable = cancellablePromise((resolve, reject) => {
setTimeout(() => {
if (!cancellable.isCancelled) {
resolve('Operation completed');
}
}, 1000);
});
cancellable.cancel(); // 取消Promise
cancellable.then(console.log).catch(console.error);
4. Promise 的顺序执行
实战场景: 有时候我们需要按顺序执行一组Promise,以确保前一个异步操作完成后再开始下一个。
const promiseTask = (promiseArr) => {
promiseArr.reduce(
(prev, next) => prev.then(() => next()),
Promise.resolve()
)
}
5. Promise 的重试
实战场景:当Promise因为某些暂时性的错误被拒绝时,可能希望能够重试执行。下面是通过递归方法 attempt(attemptNumber) 实现重试
const promiseTask = (promiseFn, maxTimes) => {
return new Promise((resolve, reject) => {
const attempt = attemptNumber => {
if (attemptNumber === maxTimes) {
reject(new Error('达到最大重试次数'));
return;
}
promiseFn().then(resolve).catch(() => {
setTimeout(() => {
attempt(attemptNumber + 1);
}, 1000);
});
};
attempt(0);
});
};
6. Promise 处理多个异步操作
Promise.all和Promise.allSettled都会等待处理所有的异步结果,但是不同的是,任何其中一个Promise 拒绝,Promise.all()会立即拒绝;而Promise.allSettled会等待所有的 Promise 解决或者拒绝再返回结果。
const promise1 = Promise.resolve('Promise 1 成功');
const promise2 = Promise.reject('Promise 2 失败');
Promise.all([promise1, promise2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.log('An error occurred in Promise.all():', error);
});
// Output:
// An error occurred in Promise.all(): Promise 2 失败
const promise1 = Promise.resolve('Promise 1 成功');
const promise2 = Promise.reject('Promise 2 失败');
Promise.allSettled([promise1, promise2]).then((results) => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(
`Promise ${index + 1} was fulfilled with value:`,
result.value
);
} else {
console.log(
`Promise ${index + 1} was rejected with reason:`,
result.reason
);
}
});
});
// Output:
// Promise 1 was fulfilled with value: Promise 1 成功
// Promise 2 was rejected with reason: Promise 2 失败
7. Promise 实现一个sleep 函数
可以通过 Promise 实现一个阻塞函数
async sleep(time) {
return new Promise((resolve, reject) => {
let timerId = setTimeout(() => {
clearTimeout(timerId);
resolve()
}, time)
})
}