2023-05-14|閱讀時間 ‧ 約 6 分鐘

JavaScript 中使用fetch的AbortController和取消功能:如何在Go 實現

有多種情況下我們需要從API取消請求:
  1. 如果我們正在建立一個支付系統,並且請求一直進行,但使用者希望立即取消這筆訂單。這就是為什麼開發者會建立這個功能的原因。
  2. 從電子商務購買商品。使用者意外關閉了頁面,無法立即返回上一頁。
  3. 訂閱取消。
......等等
這將對對這個系統沒有信心的使用者產生影響。因此,開發者需要認真對待這個問題。

我使用Vue和Go建立了一個簡單的前端系統和後端系統的示範。

後端部分
mkdir cancellation-demo && cd cancellation-demo
建立資料夾,稱為 `cancellation-demo`
go mod init callcellation-demo && \ 
touch main.go && \
go get github.com/gin-gonic/gin
從go module和main.go開始。導入gin套件。
以下是main.go的內容。建立一個簡單的Hello World。
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

func CancelHandler(ctx *gin.Context) {
 ctx.String(http.StatusOK, "hello world")
}

func main() {
 router := gin.Default()
 router.GET("/", EchoHandler)
 router.Run()
}
我們的目標是在收到請求後等待3秒,然後發送一個文字回應"Hello World"。如果使用者在3秒內取消了請求,我們將停止處理並打印結果,告知系統管理員該請求已被取消
以下範例
func CancelHandler(ctx *gin.Context) {
 ctx.String(http.StatusOK, "hello world")
}
我們改寫成
func CancelHandler(ctx *gin.Context) {
responseResult := make(chan string)

go func() {
 time.Sleep(3 * time.Second)
 responseResult <- "hello world"
}()

for {
select {
case result := <-responseResult:
  ctx.String(http.StatusOK, result)
return
case <-ctx.Request.Context().Done():
  log.Println("abort triggerred.Cancellation happen")
  ctx.AbortWithStatus(http.StatusNoContent)
return
 }
}
}
讓我來解釋這部分:
我們需要建立一個goroutine和channel來傳送資料,並使用CancelHandler接收channel和ctx.Context().Done()來傳送chan類型。
這有助於我們檢查是否在請求被取消時收到中止訊號。這個方法將由中止訊號觸發。
因為我們需要建立一個在不同端口上監聽的前端專案,這導致了跨來源資源共享(CORS)的問題。因此,我們需要建立一個中間件(Middleware)來解決這個問題。
func CorsMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
 ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
 ctx.Writer.Header().Set("Access-Control-allow-Credentials", "true")
 ctx.Writer.Header().Set("Access-Control-allow-Method", "GET, POST, OPTIONS, PUT, PATCH, DELETE")
 ctx.Writer.Header().Set("Access-Control-allow-Headers", "Authorization, Accept,Accept-Encoding, Content-Type,Content-Length, Cache-Control, Origin, X-CSRF-Token, X-Requested-With")
if ctx.Request.Method == http.MethodOptions {
  ctx.AbortWithStatus(http.StatusNoContent)
 return
}
  ctx.Next()
}
}
最後,我們的後端專案的部分就會看起來像這樣
package main

import (
"log"
"net/http"
"time"

"github.com/gin-gonic/gin"
)

func CorsMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
 ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
 ctx.Writer.Header().Set("Access-Control-allow-Credentials", "true")
 ctx.Writer.Header().Set("Access-Control-allow-Method", "GET, POST, OPTIONS, PUT, PATCH, DELETE")
 ctx.Writer.Header().Set("Access-Control-allow-Headers", "Authorization, Accept,Accept-Encoding, Content-Type,Content-Length, Cache-Control, Origin, X-CSRF-Token, X-Requested-With")
if ctx.Request.Method == http.MethodOptions {
  ctx.AbortWithStatus(http.StatusNoContent)
return
 }
 ctx.Next()
}
}

func CancelHandler(ctx *gin.Context) {
responseResult := make(chan string)

go func() {
 time.Sleep(3 * time.Second)
 responseResult <- "hello world"
}()

for {
select {
case result := <-responseResult:
  ctx.String(http.StatusOK, result)
return
case <-ctx.Request.Context().Done():
  log.Println("abort triggered")
  ctx.AbortWithStatus(http.StatusNoContent)
return
 }
}

}

func main() {
router := gin.Default()
router.Use(CorsMiddleware())
router.GET("/", CancelHandler)
router.Run()
}

現在換前端的部分
我們前端使用 vue 作為我們的前端框架
pnpm create vite --template vue-ts web && \
cd web && \
pnpm install
注意:對了,我本人個人是喜歡用 pnpm 作為我的 package manager, 不代表不能用 npm 然後我們將建立一個簡單的使用AbortController的fetch範例。這部分是向http://localhost:8080發送請求的部分。切記我們的後端專案在端口8080上監聽。
import { ref } from 'vue';
const abortReference = ref(new AbortController());

const getRequest = async () => {
  abortReference.value = new AbortController();
  try {
   const response = await fetch('http://localhost:8080', {
       method: 'GET',
      signal: abortReference.value.signal,
    })
   const mainContent = await response.text();
    console.log(mainContent);
  } catch{
   console.error('api error');
  }
}
取消功能的部分
const quitRequest = () => {
console.log('cancel request');
abortReference.value.abort();
}
現在我們把兩個方法取代原本專案的 app.vue
<script setup lang="ts">
import { ref } from 'vue';
const abortReference = ref(new AbortController());

const getRequest = async () => {
    abortReference.value = new AbortController();
  try {
  const response = await fetch('http://localhost:8080', {
    method: 'GET',
     signal: abortReference.value.signal,
    })
  const mainContent = await response.text();
  console.log(mainContent);
 } catch{
  console.error('api error');
 }
}

const quitRequest = () => {
console.log('cancel request');
   abortReference.value.abort();
}

</script>

<template>
<button @click="getRequest">
   click to request
</button>
<button @click="quitRequest">
   Abort Request
</button>
</template>
這樣就大功告成了,
pnpm dev
然後打開瀏覽器我們會看到的結果
如果有任何問題歡迎跟我討論,感謝你得閱讀
以下是我的 medium 帳號
還有我發表的英文版原文 ,如果對我的創作有興趣歡迎追蹤捧場

分享至
成為作者繼續創作的動力吧!
Hello 我是 Michael 是一名全端工程師,擅長 vue,vite, unocss,tailwindcss,golang,rust是個程式多語言開發人員
從 Google News 追蹤更多 vocus 的最新精選內容從 Google News 追蹤更多 vocus 的最新精選內容

發表回應

成為會員 後即可發表留言