你说你会Promise?那你解决一下项目中的这五个难题
作者:Sunshine_Lin
简介:「前端之神」的号主江湖人称林三心,现已有100+篇原创文章,全网粉丝高达1w+,面试过超过100+个前端程序员,全网获赞2w+,全网阅读量播放量超过60w,更是B站「面试进阶成为大佬」系列视频的Up主。喜欢分享Vue,React,Typescript等高级前端知识。
来源:SegmentFault 思否社区
前言
大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心,众所周知哈, Promise 在咱们的开发中是相当的重要,我觉得对于 Promise 的使用等级,可以分为三个等级。
1、掌握 Promise 的基本使用 2、掌握 Promise 的基本原理 3、在项目中能灵活运用 Promise 解决一些问题
接口请求超时
1、自己实现
/**
* 模拟延时
* @param {number} delay 延迟时间
* @returns {Promise<any>}
*/
function sleep(delay) {
return new Promise((_, reject) => {
setTimeout(() => reject('超时喽'), delay)
})
}
/**
* 模拟请求
*/
function request() {
// 假设请求需要 1s
return new Promise(resolve => {
setTimeout(() => resolve('成功喽'), 1000)
})
}
/**
* 判断是否超时
* @param {() => Promise<any>} requestFn 请求函数
* @param {number} delay 延迟时长
* @returns {Promise<any>}
*/
function timeoutPromise(requestFn, delay) {
return new Promise((resolve, reject) => {
const promises = [requestFn(), sleep(delay)]
for (const promise of promises) {
// 超时则执行失败,不超时则执行成功
promise.then(res => resolve(res), err => reject(err))
}
})
}
2、Promise.race
function timeoutPromise(requestFn, delay) {
// 如果先返回的是延迟Promise则说明超时了
return Promise.race([requestFn(), sleep(delay)])
}
3、测试
// 超时
timeoutPromise(request, 500).catch(err => console.log(err)) // 超时喽
// 不超时
timeoutPromise(request, 2000).then(res => console.log(res)) // 成功喽
转盘抽奖
1、转盘转完,接口还没请求回来,这是不正常的 2、转盘转完前,接口就请求完毕,这是正常的,但是需要保证 请求回调 跟 转盘转完回调 同时执行
1、转盘转完,接口还没请求回来
/**
* 模拟延时
* @param {number} delay 延迟时间
* @returns {Promise<any>}
*/
function sleep(delay) {
return new Promise((_, reject) => {
setTimeout(() => reject('超时喽'), delay)
})
}
/**
* 模拟请求
*/
function request() {
return new Promise(resolve => {
setTimeout(() => resolve('成功喽'), 1000)
})
}
/**
* 判断是否超时
* @param {() => Promise<any>} requestFn 请求函数
* @param {number} delay 延迟时长
* @returns {Promise<any>}
*/
function timeoutPromise(requestFn, delay) {
return Promise.race([requestFn(), sleep(delay)])
}
2、转盘转完前,接口就请求完毕
// ...上面代码
/**
* 模拟转盘旋转到停止的延时
* @param {number} delay 延迟时间
* @returns {Promise<any>}
*/
function turntableSleep(delay) {
return new Promise(resolve => {
setTimeout(() => resolve('停止转动喽'), delay)
})
}
/**
* 判断是否超时
* @param {() => Promise<any>} requestFn 请求函数
* @param {number} turntableDelay 转盘转多久
* @param {number} delay 请求超时时长
* @returns {Promise<any>}
*/
function zhuanpanPromise(requsetFn, turntableDelay, delay) {
return Promise.all([timeoutPromise(requsetFn, delay), turntableSleep(turntableDelay)])
}
3、测试
// 不超时,且先于转盘停止前请求回数据
zhuanpanPromise(request, 2500, 1500).then(res => console.log(res), err => console.log(err))
控制并发的Promise的调度器
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4
整个的完整执行流程:
一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
实现
class Scheduler {
constructor(limit) {
this.queue = []
this.limit = limit
this.count = 0
}
add(time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(order)
resolve()
}, time)
})
}
this.queue.push(promiseCreator)
}
taskStart() {
for(let i = 0; i < this.limit; i++) {
this.request()
}
}
request() {
if (!this.queue.length || this.count >= this.limit) return
this.count++
this.queue.shift()().then(() => {
this.count--
this.request()
})
}
}
测试
// 测试
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
取消重复请求
实现
class CancelablePromise {
constructor() {
this.pendingPromise = null
this.reject = null
}
request(requestFn) {
if (this.pendingPromise) {
this.cancel('取消重复请求')
}
const promise = new Promise((_, reject) => (this.reject = reject))
this.pendingPromise = Promise.race([requestFn(), promise])
return this.pendingPromise
}
cancel(reason) {
this.reject(reason)
this.pendingPromise = null
}
}
function request(delay) {
return () =>
new Promise(resolve => {
setTimeout(() => {
resolve('最后赢家是我')
}, delay)
})
}
测试
const cancelPromise = new CancelablePromise()
// 模拟频繁请求5次
for (let i = 0; i < 5; i++) {
cancelPromise
.request(request(2000))
.then((res) => console.log(res)) // 最后一个 最后赢家是我
.catch((err) => console.error(err)); // 前四个 取消重复请求
}
全局请求loading
1、全局只要有一个接口还在请求中,就展示 loading 2、全局所有接口都不在请求中,就隐藏 loading
实现
class PromiseManager {
constructor() {
this.pendingPromise = new Set()
this.loading = false
}
generateKey() {
return `${new Date().getTime()}-${parseInt(Math.random() * 1000)}`
}
push(...requestFns) {
for (const requestFn of requestFns) {
const key = this.generateKey()
this.pendingPromise.add(key)
requestFn().finally(() => {
this.pendingPromise.delete(key)
this.loading = this.pendingPromise.size !== 0
})
}
}
}
测试
// 模拟请求
function request(delay) {
return () => {
return new Promise(resolve => {
setTimeout(() => resolve('成功喽'), delay)
})
}
}
const manager = new PromiseManager()
manager.push(request(1000), request(2000), request(800), request(2000), request(1500))
const timer = setInterval(() => {
// 轮询查看loading状态
console.log(manager.loading)
}, 300)
参考
评论