技術實踐丨如何解決異步接口請求快慢不均致使的數據錯誤問題?

摘要:實時搜索都會面臨一個通用的問題,就是瀏覽器請求後臺接口都是異步的,若是先發起請求的接口後返回數據,列表/表格中顯示的數據就極可能會是錯亂的。

本文分享自華爲雲社區《如何解決異步接口請求快慢不均致使的數據錯誤問題?》,原文做者:Kagol 。前端

引言

搜索功能,我想不少業務都會涉及,這個功能的特色是:node

  • 用戶能夠在輸入框中輸入一個關鍵字,而後在一個列表中顯示該關鍵字對應的數據;
  • 輸入框是能夠隨時修改/刪除所有或部分關鍵字的;
  • 若是是實時搜索 (即輸入完關鍵字立刻出結果,不須要額外的操做或過多的等待),接口調用將會很是頻繁。

實時搜索都會面臨一個通用的問題,就是:ios

瀏覽器請求後臺接口都是異步的,若是先發起請求的接口後返回數據,列表/表格中顯示的數據就極可能會是錯亂的。npm

問題重現

最近測試提了一個搜索(PS:此處的搜索 就是用 DevUI 新推出的 CategorySearch 組件實現的)相關的缺陷單,就涉及到了上述問題。
image.pngjson

這個bug單大體意思是:axios

搜索的時候,連續快速輸入或者刪除關鍵字,搜索結果和搜索關鍵字不匹配。segmentfault

從缺陷單的截圖來看,本意是要搜索關鍵字8.4.7迭代】,表格中的實際搜索結果是8.4.7迭代】過關鍵字的數據。跨域

缺陷單的截圖還很是貼心地貼了兩次請求的信息:
image.png瀏覽器

做爲一名「有經驗的」前端開發,一看就是一個通用的技術問題:服務器

  1. 瀏覽器從服務器發起的請求都是異步的;
  2. 因爲前一次請求服務器返回比較慢,還沒等第一次請求返回結果,後一次請求就發起了,而且迅速返回告終果,這時表格確定顯示後一次的結果;
  3. 過了2秒,第一次請求的結果才慢吞吞地返回了,這時表格錯誤地又顯示了第一次請求的結果;
  4. 最終致使了這個bug。

怎麼解決呢?

在想解決方案以前,得想辦法必現這個問題,靠後臺接口是不現實的,大部分狀況下後臺接口都會很快返回結果。

因此要必現這個問題,得先模擬慢接口。

模擬慢接口

爲了快速搭建一個後臺服務,並模擬慢接口,咱們選擇 Koa 這個輕量的 Node 框架。

快速開始

Koa 使用起來很是方便,只須要:

  1. 新建項目文件夾:mkdir koa-server
  2. 建立 package.json:npm init -y
  3. 安裝 Koa:npm i koa
  4. 編寫服務代碼:vi app.js
  5. 啓動:node app.js
  6. 訪問:http://localhost:3000/

編寫服務代碼

使用如下命令建立 app.js 啓動文件:

vi app.js
在文件中輸入如下 3 行代碼,便可啓動一個 Koa 服務:

const Koa = require('koa'); // 引入 Koa
const app = new Koa(); // 建立 Koa 實例
app.listen(3000); // 監聽 3000 端口

訪問

若是沒有在3000端口啓動任務服務,在瀏覽器訪問:

http://localhost:3000/

會顯示如下頁面:
image.png

啓動了咱們的 Koa Server 以後,訪問:

http://localhost:3000/

會顯示:
image.png

get 請求

剛纔搭建的只是一個空服務,什麼路由都沒有,因此顯示了Not Found。

咱們能夠經過中間件的方式,讓咱們的 Koa Server 顯示點兒東西。

因爲要增長一個根路由,咱們先安裝路由依賴

npm i koa-router
而後引入 Koa Router

const router = require('koa-router')();
接着是編寫get接口

app.get('/', async (ctx, next) => {
  ctx.response.body = '<p>Hello Koa Server!</p>';
});

最後別忘了使用路由中間件

app.use(router.routes());
改完代碼須要重啓 Koa 服務,爲了方便重啓,咱們使用 pm2 這個 Node 進程管理工具來啓動/重啓 Koa 服務,使用起來也很是簡單:

  • 全局安裝 pm2:npm i -g pm2
  • 啓動 Koa Server:pm2 start app.js
  • 重啓 Koa Server:pm2 restart app.js

重啓完 Koa Server,再次訪問

http://localhost:3000/

會顯示如下內容:
image.png

post 請求

有了以上基礎,就能夠寫一個 post 接口,模擬慢接口啦!

編寫 post 接口和 get 接口很相似:

router.post('/getList', async (ctx, next) => {
  ctx.response.body = {
    status: 200,
    msg: '這是post接口返回的測試數據',
    data: [1, 2, 3]
  };
});

這時咱們可使用 Postman 調用下這個 post 接口,如期返回:
image.png

容許跨域

咱們嘗試在 NG CLI 項目裏調用這個 post 接口:

this.http.post('http://localhost:3000/getList', {
  id: 1,
}).subscribe(result => {
  console.log('result:', result);
});

可是在瀏覽器裏直接調用,卻得不到想要的結果:

  • result 沒有打印出來
  • 控制檯報錯
  • Network請求也是紅色的
    image.png

因爲本地啓動的項目端口號(4200)和 Koa Server 的(3000)不一樣,瀏覽器認爲這個接口跨域,所以攔截了。

NG CLI 項目本地連接:

http://localhost:4200/

Koa Server 連接:

http://localhost:3000/

Koa 有一箇中間件能夠容許跨域:koa2-cors

這個中間件的使用方式,和路由中間件很相似。

先安裝依賴:

npm i koa2-cors
而後引入:

const cors = require('koa2-cors');
再使用中間件:

app.use(cors());
這時咱們再去訪問:

http://localhost:4200/

就能獲得想要的結果啦!
image.png

慢接口

post 接口已經有了,怎麼模擬慢接口呢?

其實就是但願服務器延遲返回結果。

在 post 接口以前增長延遲的邏輯:

async function delay(time) {
    return new Promise(function(resolve, reject) { 
      setTimeout(function() {
        resolve();
      }, time);
    });
  }
  await delay(5000); // 延遲 5s 返回結果
  ctx.response.body = { ... };

再次訪問 getList 接口,發現前面接口會一直pending,5s 多才真正返回結果。
image.png
image.png

取消慢接口請求

能模擬慢接口,就能輕易地必現測試提的問題啦!

先必現這個問題,而後嘗試修復這個問題,最後看下這個問題還出不出現,不出現說明咱們的方案能解決這個bug,問題還有說明咱們得想別的辦法。

這是修復bug正確的打開方式。

最直觀的方案就是再發起第二次請求以後,若是第一次請求未返回,那就直接取消此次請求,使用第二次請求的返回結果。

怎麼取消一次http請求呢?

Angular 的異步事件機制是基於 RxJS 的,取消一個正在執行的 http 請求很是方便。

前面已經看到 Angular 使用 HttpClient 服務來發起 http 請求,並調用subscribe 方法來訂閱後臺的返回結果:

this.http.post('http://localhost:3000/getList', {
  id: 1,
}).subscribe(result => {
  console.log('result:', result);
});

要取消 http 請求,咱們須要先把這個訂閱存到組件一個變量裏:

private getListSubscription: Subscription;

this.getListSubscription = this.http.post('http://localhost:3000/getList', {
  id: 1,
}).subscribe(result => {
  console.log('result:', result);
});

而後在從新發起 http 請求以前,取消上一次請求的訂閱便可。

this.getListSubscription?.unsubscribe(); // 從新發起 http 請求以前,取消上一次請求的訂閱

this.getListSubscription = this.http.post(...);

其餘 http 庫如何取消請求

至此這個缺陷算是解決了,其實這是一個通用的問題,不論是在什麼業務,使用什麼框架,都會遇到異步接口慢致使的數據錯亂問題。

那麼,若是使用 fetch 這種瀏覽器原生的 http 請求接口或者 axios 這種業界普遍使用的 http 庫,怎麼取消正在進行的 http 請求呢?

fetch

先來看下 fetch,fetch 是瀏覽器原生提供的 AJAX 接口,使用起來也很是方便。

使用 fetch 發起一個 post 請求:

fetch('http://localhost:3000/getList', {
   method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify({
    id: 1
  })
}).then(result => {
  console.log('result', result);
});

可使用 AbortController 來實現請求取消:

this.controller?.abort(); // 從新發起 http 請求以前,取消上一次請求

const controller = new AbortController(); //  建立 AbortController 實例
const signal = controller.signal;
this.controller = controller;

fetch('http://localhost:3000/getList', {
   method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify({
    id: 1
  }),
  signal, // 信號參數,用來控制 http 請求的執行
}).then(result => {
  console.log('result', result);
});

axios

再來看看 axios,先看下如何使用 axios 發起 post 請求。

先安裝:

npm i axios
再引入:

import axios from 'axios';
發起 post 請求:

axios.post('http://localhost:3000/getList', {
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  data: {
    id: 1,
  },
})
.then(result => {
  console.log('result:', result);
});

axios 發起的請求能夠經過 cancelToken 來取消。

this.source?.cancel('The request is canceled!');

this.source = axios.CancelToken.source(); // 初始化 source 對象

axios.post('http://localhost:3000/getList', {
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  data: {
    id: 1,
  },
}, { // 注意是第三個參數
  cancelToken: this.source.token, // 這裏聲明的 cancelToken 其實至關因而一個標記或者信號
})
.then(result => {
  console.log('result:', result);
});

小結

本文經過實際項目中遇到的問題,總結缺陷分析和解決的通用方法,並對異步接口請求致使的數據錯誤問題進行了深刻的解析。

點擊關注,第一時間瞭解華爲雲新鮮技術~

相關文章
相關標籤/搜索