问题
有个需求,某个异步请求函数可能被多次调用,重复调用消耗资源,需要对其进行优化
- 每次调用该函数都能取到返回值
- 只发送一次异步请求
这个和节流、防抖功能不一样,节流防抖会丢弃掉中间的请求,中间的请求获取不到返回值,这里要求每一个函数调用都能取到返回值。
方案一
很容易想到使用同步非阻塞方案,在第一个点击时,进入loading状态,之后的点击判断loading就等50毫秒继续检查loading的值,直到loading为false,返回localEnv。这里使用setTimeout来模拟异步请求。
<body>
<input onclick="clickMe()" type="button" value="点我">
</body>
async function clickMe() {
const env = await getEnv()
console.log(env)
}
let localEnv
let loading = false
async function getEnv() {
if (localEnv) {
return localEnv
}
// 若正在请求中,则每50毫秒检查loading状态,直到为false,返回请求结果
if (loading) {
// 同步非阻塞
while(loading) {
await wait(50)
}
return localEnv
} else {
loading = true
return new Promise((resolve, reject) => {
console.log('questing env......')
setTimeout(() => {
localEnv = {
platform: '141001',
appid: 'aoshdakhpa8fkdng'
}
loading = false
resolve(localEnv)
}, 2000)
})
}
}
function wait (time) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, time)
})
}
方案一主要由于循环去检测loading的状态,导致不那么高效
方案二
Java中可以通过锁机制,使用wait/notify轻易实现该功能。JavaScript也可以利用锁机制来实现类似wait/notify的功能。
JavaScript可以通过resolve/reject函数来实现锁,在没有调用resolve/reject函数时,promise会看起来相当于一直阻塞(其实和Java的阻塞不一样,这里只是没有执行后续的函数)。
<body>
<input onclick="clickMe()" type="button" value="点我">
</body>
async function clickMe() {
const env = await getEnv()
console.log(env)
}
let localEnv, p
const queue = []
let loading = false
async function getEnv() {
return new Promise((resolve, reject) => {
if (localEnv) {
resolve(localEnv)
}
// 进入这个if的Promise没有调用resolve(),会一直阻塞
if (loading) {
// 把resolve存入数组,待请求返回后执行resolve
queue.push({
resolve, reject})
}
if (!loading && !localEnv) {
loading = true
console.log('questing env......')
setTimeout(() => {
localEnv = {
platform: '141001',
appid: 'aoshdakhpa8fkdng'
}
loading = false
resolve(localEnv)
// 异步请求结束,调用所有正在阻塞的Promise的resolve函数,返回结果,解除阻塞
while (p = queue.shift()) {
p.resolve(localEnv)
}
}, 2000)
}
})
}
结语
利用好Promise没有resolve/reject会一直阻塞的特性,可以实现类似Java的wait/notify功能,实现同一时间多次异步请求函数都取到返回值,只触发一次异步请求的功能。