问题

有个需求,某个异步请求函数可能被多次调用,重复调用消耗资源,需要对其进行优化

  1. 每次调用该函数都能取到返回值
  2. 只发送一次异步请求

这个和节流、防抖功能不一样,节流防抖会丢弃掉中间的请求,中间的请求获取不到返回值,这里要求每一个函数调用都能取到返回值。

方案一

很容易想到使用同步非阻塞方案,在第一个点击时,进入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功能,实现同一时间多次异步请求函数都取到返回值,只触发一次异步请求的功能。