取消(中止)异步请求

问题描述:动态获取图片宽、高。由于图片大小不一,导致异步请求返回时间有差异,频繁操作导致渲染结果出现问题。
abort-opt-1.png
在这里插入图片描述

举例说明:【图片2】尺寸较大,返回需要【2s】;【图片1】尺寸最小,返回需要【1s】。先点击【图片2】然后迅速点击【图片1】,最后渲染和获取的结果为【图片2】==> 最后操作的是【图片1】,按预期应该展示【图片1】。
在这里插入图片描述
该问题,在两个以上异步请求控制同一展示内容时都会出现: 第一个异步请求(耗时长)发出没有返回,又操作触发了第二个请求(耗时短)。第二个请求先返回,内容呈现;此时第一个异步请求才返回,导致了内容呈现成第一个请求的结果。和操作预期不符(期望呈现的是后点击的–第二个请求的结果)。

  1. 如果只有异步请求,可以采用 abort(取消/中止) 请求方式
  2. 异步请求之后还涉及到其他异步操作,需要采用特殊方式处理(本文重点

上述示例代码实现

// 显示图片尺寸
async function showImageSize(id) {
  const imageBlob = await fetch(new Request(`http://localhost:8888/images?name=${id}`)).then(r => r.blob())
  const img = await createImageBitmap(imageBlob)
  render(img)
}

document.querySelector('#btn').onclick = () => showImageSize(id)

function render(img) {
  // document.querySelector('img').src = URL.createObjectURL(imageBlob)
  document.querySelector('#result').innerHTML = `${img.width}px * ${img.height}px`
}

这里,对于 fetch 增加中止请求很简单

const controller = new AbortController()
const { signal } = controller
const imageBlob = await fetch(
  new Request(`http://localhost:8888/images?name=${id}`),
  { signal }
).then(r => r.blob())

// 要求取消 fetch 请求,在相应的时机调取即可
controller.abort()

abort() 被调用时,fetch() promise 拒绝一个名为 AbortErrorDOMException

但问题是,createImageBitmap() 不支持中止操作。 我们通过辅助函数形式进行处理:

async function abortable(signal, promise) {
  if (signal.aborted) throw new DOMException('AbortError', 'AbortError')
  return Promise.race([
    promise,
    new Promise((resolve, reject) => {
      signal.addEventListener('abort', () => {
        reject(new DOMException('AbortError', 'AbortError'));
      })
    })
  ])
}

修改 showImageSize() 函数

let controller

async function showImageSize(id) {
  if (controller) controller.abort()
  
  try {
    controller = new AbortController()
    const { signal } = controller
    const imageBlob = await fetch(
      new Request(`http://localhost:8888/images?name=${id}`),
      { signal }
    ).then(r => r.blob())

    const img = await abortable(signal, createImageBitmap(imageBlob)) 
  } catch (error) {
    if (error.name === 'AbortError') return
    throw error;
  }
}

后端服务(模拟)

const url = require('url')
const path = require('path')
const fs = require('fs')

server.on('request', (req, res) => {
  req.on('end', () => {
    const querys = url.parse(req.url).query
    // 可查看相关「Form 表单数据编码、解码」文章
    const searchParams = new URLSearchParams(querys)
    const fileName = searchParams.get('name')

    const filePath = path.resolve(__dirname, `../images/${fileName}.png`)
    const cs = fs.createReadStream(filePath)
    cs.on("data", chunk => {
      res.write(chunk)
    })
    cs.on("end", () => {
      res.end()
    })
  })
})
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋飛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值