你說你會(huì)Promise?那你解決一下項(xiàng)目中的這五個(gè)難題
作者:Sunshine_Lin
簡(jiǎn)介:「前端之神」的號(hào)主江湖人稱林三心,現(xiàn)已有100+篇原創(chuàng)文章,全網(wǎng)粉絲高達(dá)1w+,面試過超過100+個(gè)前端程序員,全網(wǎng)獲贊2w+,全網(wǎng)閱讀量播放量超過60w,更是B站「面試進(jìn)階成為大佬」系列視頻的Up主。喜歡分享Vue,React,Typescript等高級(jí)前端知識(shí)。
來源:SegmentFault 思否社區(qū)
前言
大家好,我是林三心,用最通俗易懂的話講最難的知識(shí)點(diǎn)是我的座右銘,基礎(chǔ)是進(jìn)階的前提是我的初心,眾所周知哈, Promise 在咱們的開發(fā)中是相當(dāng)?shù)闹匾?,我覺得對(duì)于 Promise 的使用等級(jí),可以分為三個(gè)等級(jí)。
1、掌握 Promise 的基本使用 2、掌握 Promise 的基本原理 3、在項(xiàng)目中能靈活運(yùn)用 Promise 解決一些問題
接口請(qǐng)求超時(shí)
1、自己實(shí)現(xiàn)

/**
* 模擬延時(shí)
* @param {number} delay 延遲時(shí)間
* @returns {Promise<any>}
*/
function sleep(delay) {
return new Promise((_, reject) => {
setTimeout(() => reject('超時(shí)嘍'), delay)
})
}
/**
* 模擬請(qǐng)求
*/
function request() {
// 假設(shè)請(qǐng)求需要 1s
return new Promise(resolve => {
setTimeout(() => resolve('成功嘍'), 1000)
})
}
/**
* 判斷是否超時(shí)
* @param {() => Promise<any>} requestFn 請(qǐng)求函數(shù)
* @param {number} delay 延遲時(shí)長(zhǎng)
* @returns {Promise<any>}
*/
function timeoutPromise(requestFn, delay) {
return new Promise((resolve, reject) => {
const promises = [requestFn(), sleep(delay)]
for (const promise of promises) {
// 超時(shí)則執(zhí)行失敗,不超時(shí)則執(zhí)行成功
promise.then(res => resolve(res), err => reject(err))
}
})
}
2、Promise.race
function timeoutPromise(requestFn, delay) {
// 如果先返回的是延遲Promise則說明超時(shí)了
return Promise.race([requestFn(), sleep(delay)])
}
3、測(cè)試
// 超時(shí)
timeoutPromise(request, 500).catch(err => console.log(err)) // 超時(shí)嘍
// 不超時(shí)
timeoutPromise(request, 2000).then(res => console.log(res)) // 成功嘍
轉(zhuǎn)盤抽獎(jiǎng)
1、轉(zhuǎn)盤轉(zhuǎn)完,接口還沒請(qǐng)求回來,這是不正常的 2、轉(zhuǎn)盤轉(zhuǎn)完前,接口就請(qǐng)求完畢,這是正常的,但是需要保證 請(qǐng)求回調(diào) 跟 轉(zhuǎn)盤轉(zhuǎn)完回調(diào) 同時(shí)執(zhí)行
1、轉(zhuǎn)盤轉(zhuǎn)完,接口還沒請(qǐng)求回來
/**
* 模擬延時(shí)
* @param {number} delay 延遲時(shí)間
* @returns {Promise<any>}
*/
function sleep(delay) {
return new Promise((_, reject) => {
setTimeout(() => reject('超時(shí)嘍'), delay)
})
}
/**
* 模擬請(qǐng)求
*/
function request() {
return new Promise(resolve => {
setTimeout(() => resolve('成功嘍'), 1000)
})
}
/**
* 判斷是否超時(shí)
* @param {() => Promise<any>} requestFn 請(qǐng)求函數(shù)
* @param {number} delay 延遲時(shí)長(zhǎng)
* @returns {Promise<any>}
*/
function timeoutPromise(requestFn, delay) {
return Promise.race([requestFn(), sleep(delay)])
}
2、轉(zhuǎn)盤轉(zhuǎn)完前,接口就請(qǐng)求完畢
// ...上面代碼
/**
* 模擬轉(zhuǎn)盤旋轉(zhuǎn)到停止的延時(shí)
* @param {number} delay 延遲時(shí)間
* @returns {Promise<any>}
*/
function turntableSleep(delay) {
return new Promise(resolve => {
setTimeout(() => resolve('停止轉(zhuǎn)動(dòng)嘍'), delay)
})
}
/**
* 判斷是否超時(shí)
* @param {() => Promise<any>} requestFn 請(qǐng)求函數(shù)
* @param {number} turntableDelay 轉(zhuǎn)盤轉(zhuǎn)多久
* @param {number} delay 請(qǐng)求超時(shí)時(shí)長(zhǎng)
* @returns {Promise<any>}
*/
function zhuanpanPromise(requsetFn, turntableDelay, delay) {
return Promise.all([timeoutPromise(requsetFn, delay), turntableSleep(turntableDelay)])
}
3、測(cè)試
// 不超時(shí),且先于轉(zhuǎn)盤停止前請(qǐng)求回?cái)?shù)據(jù)
zhuanpanPromise(request, 2500, 1500).then(res => console.log(res), err => console.log(err))
控制并發(fā)的Promise的調(diào)度器
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的輸出順序是:2 3 1 4
整個(gè)的完整執(zhí)行流程:
一開始1、2兩個(gè)任務(wù)開始執(zhí)行
500ms時(shí),2任務(wù)執(zhí)行完畢,輸出2,任務(wù)3開始執(zhí)行
800ms時(shí),3任務(wù)執(zhí)行完畢,輸出3,任務(wù)4開始執(zhí)行
1000ms時(shí),1任務(wù)執(zhí)行完畢,輸出1,此時(shí)只剩下4任務(wù)在執(zhí)行
1200ms時(shí),4任務(wù)執(zhí)行完畢,輸出4
實(shí)現(xiàn)
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()
})
}
}
測(cè)試
// 測(cè)試
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();
取消重復(fù)請(qǐng)求
實(shí)現(xiàn)
class CancelablePromise {
constructor() {
this.pendingPromise = null
this.reject = null
}
request(requestFn) {
if (this.pendingPromise) {
this.cancel('取消重復(fù)請(qǐng)求')
}
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)
})
}
測(cè)試
const cancelPromise = new CancelablePromise()
// 模擬頻繁請(qǐng)求5次
for (let i = 0; i < 5; i++) {
cancelPromise
.request(request(2000))
.then((res) => console.log(res)) // 最后一個(gè) 最后贏家是我
.catch((err) => console.error(err)); // 前四個(gè) 取消重復(fù)請(qǐng)求
}
全局請(qǐng)求loading
1、全局只要有一個(gè)接口還在請(qǐng)求中,就展示 loading 2、全局所有接口都不在請(qǐng)求中,就隱藏 loading
實(shí)現(xiàn)
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
})
}
}
}測(cè)試
// 模擬請(qǐng)求
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狀態(tài)
console.log(manager.loading)
}, 300)參考

評(píng)論
圖片
表情
