likes
comments
collection
share

如何解决前端的“竞态问题”

作者站长头像
站长
· 阅读数 53

走过路过发现 bug 请指出,拯救一个辣鸡(但很帅)的少年就靠您啦!!!

什么是竞态问题

竞争问题 race conditions 是并发编程的一种情况,其中两个并发线程或进程竞争资源,最终状态取决于谁先获得资源。

在前端开发中,我们经常这种情况,当发送两个请求时,由于网络原因,如果第一次请求后返回,第二次请求先返回,会导致最后保存的结果是先发送的请求,第二次的请求结果会被覆盖。具体场景比如,我们在输入时查询待选项,又比如我们先点击分页2再点击分页3。

解决思路

1、防抖

我们输入时如果先输入了 “A” 又删除输入了 “B”,如果我们最终展示的是 “A” 相关的待选项,肯定会给用户造成困扰。这是个非常典型的节流应用场景,我们可以等到用户停止输入再发送请求,防止了多次请求的发出也就避免了竞态问题。

2、设置 loading

如果我们在点击分页后进行页面 loading 状态展示,无法再次点击分页,等到请求返回后再允许发送下一次请求,也同样可以避免分页问题。

3、忽略过期请求

如果无法避免竞态问题,我们就需要直面问题并解决——忽略上一次请求的返回结果(当然也可以直接取消请求)。

忽略过期请求,这个是我最开始想到的思路,当我们发送第二个请求时,上一次的请求就应该过期了,我们如果发现请求过期就取消操作。

当然重点是如何判断过期。我们可以通过一个 id 保存最新的请求数,当返回结果后根据 id 判断是否为最新请求。

function mockFetch() {
  return new Promise((resolve) => {
    setTimeout(resolve, 300)
  })
}
// 不断递加 用于记录最新的请求id
let id = 0
// 请求函数
async function request() { 
  const currentId = (id = id + 1)
  console.log('request start', currentId)
  await mockFetch()
  if (currentId === id) {
    console.log('request end', currentId)
  } else {
    console.log('request expired', currentId, id)
  }
}
request()
request()
/*
request start 1
request start 2
request expired 1 2
request end 2
*/

这里的 mockRequest 模拟了异步请求,我们连续发送两次请求,第一次请求返回时,该次请求的 id 已经小于最新 id,这意味着下一个请求已经被发送,当前请求已经过期了。当我们发现请求过期就可以忽略请求结果。

如何让上述逻辑变得通用呢?很自然的想到把当前的全局变量 id 存到闭包中。

function raceFn(fn) {
  let id = 0

  async function wrapper(...args) {
    const currentId = (id = id + 1)
    const res = await fn(...args)
    if (currentId === id) {
      return res
    }
    // 请求过期就报错
    throw new Error('request expired')
  }

  return wrapper
}

function mockFetch() {
  return new Promise((resolve) => {
    setTimeout(resolve('success!'), 300)
  })
}

const raceMockFetch = raceFn(mockFetch)

async function request() {
  try {
    const res = await raceMockFetch()
    console.log(res);
  } catch (e) {
    console.log('request expired.', e)
  }
}
request() // request expired. Error: request expired
request() // success!

把请求函数进行处理,如果过期了就报错,否则正常返回结果。

转载自:https://juejin.cn/post/7194730444812714040
评论
请登录