前端竞态问题
- say在前面的
- 什么是竞态问题
-
- 使用await可以解决吗?
- 解决方法
-
say在前面的
- 之前波煮在学习promise的时候尚硅谷的网课中有讲,但是实在有些久远了,导致波煮今天看到竞态问题的时候都不知道它的意思,所以今天这篇博客就当提醒自己!
什么是竞态问题
- 前端竞态问题的来源主要是网络请求的异步特点,两次或者多次网络请求同时在等待响应(也就是pending状态),由于异步是不知道响应顺序的,所有就会导致最终状态可能出乎了你的意料。
- 比如我这里用
promise + settimeout以及随机数给大家模拟一下:这里我用两个按钮分别表示页面1和页面2,由于在项目中我们页面的数据一般都是网络请求得到然后动态渲染的,所以在快速切换页面时可能会出现前一个请求还未响应,第二个响应了,此时数据已经渲染了,接着第一个请求响应,于是页面数据会再次变化
核心代码
btn1.addEventListener("click", () => {randomDelay = Math.floor(Math.random() * 2000);new Promise((resolve, reject) => {setTimeout(() => {resolve("页面1");}, randomDelay);}).then((res) => {currentPage.textContent = res;console.log("页面一数据返回")});});btn2.addEventListener("click", () => {randomDelay = Math.floor(Math.random() * 2000);new Promise((resolve, reject) => {setTimeout(() => {resolve("页面2");}, randomDelay);}).then((res) => {currentPage.textContent = res;console.log("页面二数据返回")});});
效果展示
- 这里我一共点了三次按钮
- 第一次:点击了页面1按钮
- 第二次:点击了页面2按钮
- 第三次:点击了页面1按钮
- 按照我们的期待的效果应该是:最后的页面应该是页面1,但是这里确是页面2,这就是所谓的竞态问题

使用await可以解决吗?
- 写这篇的时候本人突然想到”诶,await不就是要等到响应后才会进行下一步操作吗“,那它可以解决我刚刚说的竞态问题吗?
- 答案当然是不能了,
await只能保证”当前函数内部按照顺序执行“,多次独立事件产生的异步任务仍然可能交叉完成。
解决方法
- OK,现在我们知道了是什么是竞态问题,那么我们来讲讲解决方法。
- 我们很容易就能想到一种方案,就是只执行我们最后一次的请求,之前的请求我们手动给他们撤销,我们知道fetch或者axios里面都封装了请求取消的方法,这时候我们就可以用到他们。
- 第二种方案就是给每一个请求加入一个唯一id,收到请求响应之后只处理最新id的请求,将其他请求选择性忽略。
方法一
- 因为这里我用到的是promise模拟,所以我们需要用到
AbortController,自己来封装一个可以取消的promise,代码如下:
function createCancellableRequest(pageName, delay) {return new Promise((resolve, reject) => {const controller = new AbortController();const signal = controller.signal;if (currentController) {currentController.abort();}currentController = controller;signal.addEventListener('abort', () => {reject(new Error('请求被取消'));});setTimeout(() => {if (signal.aborted) {reject(new Error('请求被取消'));return;}resolve(pageName);}, delay);});}
- 接着使用这是封装好的promise进行模拟请求,就可以解决竞态问题了。
方法二
- 第二种方法就是使用唯一id标识每个模拟请求,只处理最后一次请求的方法
- 代码如下:
let currentRequestId = 0;function createRequestWithId(pageName, delay) {const requestId = ++currentRequestId;return new Promise((resolve) => {setTimeout(() => {resolve({ pageName, requestId });}, delay);});}