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

閱讀時間約 13 分鐘

有多種情況下我們需要從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 帳號
還有我發表的英文版原文 ,如果對我的創作有興趣歡迎追蹤捧場
如果對網頁開發,區塊鏈開發,手機應用開發,桌面應用開發有興趣的可以來看看
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Michael 的程式平台 的其他內容
前言介紹 在 golang 1.16之後官方提供的工具包裡面有個 `embed` 可以使用,這使得把檔案嵌入 golang 的二進制編譯更為容易,以至於方便我們部署一些並非 .go 的副檔名檔案。 這裡要介紹的是如何把 vue 作為前端,編譯至我們的 golang 專案內,起一個網頁服務。 在開始
前言介紹 在 golang 1.16之後官方提供的工具包裡面有個 `embed` 可以使用,這使得把檔案嵌入 golang 的二進制編譯更為容易,以至於方便我們部署一些並非 .go 的副檔名檔案。 這裡要介紹的是如何把 vue 作為前端,編譯至我們的 golang 專案內,起一個網頁服務。 在開始
你可能也想看
Google News 追蹤
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
更新專案的指令有: 01 `git fetch` 來對專案做「更新 Update」。 02 `git push` 來分享你對專案的「變更 Change」[3]。 03 `git remote` 來管理遠端的儲存庫。 有趣的是,fetch 這個字源於古英語的"fetan", 表示拿來,取回來的意思。
Thumbnail
GOB Go官方有提供net/rpc的RPC套件。此套件提供GOB的編/解碼,且支援TCP或HTTP傳輸方式。它可以在伺服器端註冊多個不同類型物件。 遠端存取的要求條件 方法的類型可輸出 方法的本體可輸出 方法必須要有兩個參數是輸出或內建 方法的第二個參數是指標型 方法的返回類型為
xhr 在下面的例子裡,我們首先建立了一個 XMLHttpRequest 物件,並使用 .open() 開啟一個 URL,最後使用 .send() 發出 request。 具體來說步驟有四個: 建立XMLHttpReque 開啟一個請求。 送出請求。 拿到回應後去處理畫面要如何呈現。
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
Thumbnail
之前在【什麼是網路請求(HTTP response)】筆記裡有提到,網路請求遇到 CORS 跨域問題,在開發時可以透過 vite 的反向代理來解決,那麼什麼是反向代理,有反向代理的話是不是也有正向代理呢?
Thumbnail
在開發前後端分離架構時,使用兩個不同網域所遇到跨域請求問題。特別是在POST請求時行為差異大,揭示了「簡單請求」與「預檢請求」的關鍵差異。簡單請求不需預檢,但application/json會觸發預檢請求,需透過特定設定解決。分享這篇文章希望幫助開發者有效處理跨域問題。
發送表單用get跟post看起來好像都無所謂,然而事實並非如此,使用GET的風險如下: 安全性問題 機密資訊為何不宜用GET,是因為由GET方法提交的表單會將欄位的key,value顯示於URL上,想像一下如果小明借用你的電腦,查看你的網頁歷史紀錄時就可以看到你的帳密了,多可怕! 再來就是如果
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
更新專案的指令有: 01 `git fetch` 來對專案做「更新 Update」。 02 `git push` 來分享你對專案的「變更 Change」[3]。 03 `git remote` 來管理遠端的儲存庫。 有趣的是,fetch 這個字源於古英語的"fetan", 表示拿來,取回來的意思。
Thumbnail
GOB Go官方有提供net/rpc的RPC套件。此套件提供GOB的編/解碼,且支援TCP或HTTP傳輸方式。它可以在伺服器端註冊多個不同類型物件。 遠端存取的要求條件 方法的類型可輸出 方法的本體可輸出 方法必須要有兩個參數是輸出或內建 方法的第二個參數是指標型 方法的返回類型為
xhr 在下面的例子裡,我們首先建立了一個 XMLHttpRequest 物件,並使用 .open() 開啟一個 URL,最後使用 .send() 發出 request。 具體來說步驟有四個: 建立XMLHttpReque 開啟一個請求。 送出請求。 拿到回應後去處理畫面要如何呈現。
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
Thumbnail
之前在【什麼是網路請求(HTTP response)】筆記裡有提到,網路請求遇到 CORS 跨域問題,在開發時可以透過 vite 的反向代理來解決,那麼什麼是反向代理,有反向代理的話是不是也有正向代理呢?
Thumbnail
在開發前後端分離架構時,使用兩個不同網域所遇到跨域請求問題。特別是在POST請求時行為差異大,揭示了「簡單請求」與「預檢請求」的關鍵差異。簡單請求不需預檢,但application/json會觸發預檢請求,需透過特定設定解決。分享這篇文章希望幫助開發者有效處理跨域問題。
發送表單用get跟post看起來好像都無所謂,然而事實並非如此,使用GET的風險如下: 安全性問題 機密資訊為何不宜用GET,是因為由GET方法提交的表單會將欄位的key,value顯示於URL上,想像一下如果小明借用你的電腦,查看你的網頁歷史紀錄時就可以看到你的帳密了,多可怕! 再來就是如果
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息