天天看点

探索JavaScript中的“假暂停”机制

作者:李游Leo

了解JavaScript中请求的暂停机制是软件开发过程中的一个重要知识点。在这篇文章中,我们将会通过深入的讨论和探索来解答这个问题:“JS发起的请求可以暂停吗?”

探索JavaScript中的“假暂停”机制

首先,我们需要明确这个问题包含的两个关键概念:一是“暂停”的定义是什么?二是所谓的"JS发起的请求"指的是什么?

什么是“暂停”?

"暂停",或者说是“暂时停止”,指的是在一个已经开始但未结束的过程中的临时停止。这意味着这个过程可以在某个时间点中断,然后在另一个时间点重新恢复。

JS发起的请求是什么?

要回答这个问题,我们需要先简单了解一下TCP/IP网络模型。网络模型从上到下分为应用层、传输层、网络层和网络接口层。在每次网络传输中,应用数据在发送到目标之前,都需要通过网络模型的每一层进行包装。这就像寄快递一样,我们需要先打包物品、确认包裹的大小,然后将包裹装进盒子、登记目的地,最后将包裹装上车,送往目的地。

探索JavaScript中的“假暂停”机制

网络传输示意图

在这里,“请求(Request)”可以被理解为客户端通过多次数据网络传输,将单份数据完整地发送给服务端的行为。而服务端对某次请求发送的回应数据,可以被称之为“响应(Response)”。

理论上来说,应用层的协议可以通过各种手段,比如标记数据包的序列号,来实现暂停的机制。但是,TCP协议并不支持暂停。TCP协议的数据传输是流式的,数据被视为一连串的字节流。客户端发送的数据会被拆分成多个TCP段,这些段在网络中是独立传输的,因此无法直接控制每个TCP段的传输,也就无法实现暂停请求或者暂停响应的功能。

探索JavaScript中的“假暂停”机制

解答提问

如果所说的“请求”是指网络模型中的一次请求传输,那么很明显,这样的请求是无法被暂停的。

但是,如果我们从JS发起的请求的角度来看这个问题,那问题中的“请求”,更可能是指JS运行时中发起的XMLHttpRequest或fetch请求。既然请求已经发起,那么问题自然就变成了“响应是否可以被暂停”。

我们都知道,像大文件的分片上传、分片下载等功能,本质上是将分片顺序定好之后按顺序请求,然后通过中断顺序并记录中断点来实现暂停和重传的机制。然而,对于单个请求来说,并没有这样的环境。

用JS实现“假暂停”机制

虽然我们无法真正实现请求的暂停,但我们可以模拟一个“假暂停”的功能。在前端的业务场景中,数据并不是一接收到就可以直接展示给用户的。前端开发者需要对这些数据进行处理后,才能渲染到界面上。如果我们能在请求发起之前增加一个控制器,在请求返回时,如果控制器处于暂停状态,则不处理数据,等到控制器恢复后再进行处理。这样也能达到我们的目的。接下来,我们会尝试一下如何实现这样一个假暂停的功能。

我们可以设计一个控制器Promise,和请求一起被Promise.all包裹起来。当fetch完成时,判断这个控制器的暂停状态,如果没有被暂停,那么控制器就可以直接resolve,整个Promise.all也随之resolve。

下面是一段具体的代码实现:

function _request () {
  return new Promise<number>((res) => setTimeout(() => {
    res(123)
  }, 3000))
}


function createPauseControllerPromise () {
  const result = {
    isPause: false,
    resolveWhenResume: false,
    resolve (value?: any) {},
    pause () {
      this.isPause = true
    },
    resume () {
      if (!this.isPause) return
      this.isPause = false
      if (this.resolveWhenResume) {
          this.resolve()
      }
    },
    promise: Promise.resolve()
  }
  const promise = new Promise<void>((res) => {
    result.resolve = res
  })
  result.promise = promise


  return result
}


function requestWithPauseControl <T extends () => Promise<any>>(request: T) {
  const controller = createPauseControllerPromise()


  const controlRequest = request().then((data) => {
      if (!controller.isPause) controller.resolve()
      return data
  }).finally(() => {
      controller.resolveWhenResume = true
  })


  const result = Promise.all([controlRequest, controller.promise]).then(data => {
      controller.resolve()
      return data[0]
  });


  (result as any).pause = controller.pause.bind(controller);
  (result as any).resume = controller.resume.bind(controller);


  return result as ReturnType<T> & { pause: () => void, resume: () => void }
}

           

我们可以通过调用requestWithPauseControl(_request)来替代调用_request,通过返回的pause和resume方法控制暂停和继续。

用法

在我们的示例中,我们将模拟一个情景,假设你正在请求一个巨大的JSON文件,这可能需要一些时间。然后,我们将实现一个按钮,用户可以点击它来暂停和恢复请求。

// 创造我们的"大"请求
function bigRequest() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ data: "This is a big JSON file." });
        }, 5000);
    });
}


// 使用我们的暂停控制函数
const controlledRequest = requestWithPauseControl(bigRequest);


// 创建暂停/恢复按钮
const pauseButton = document.createElement("button");
pauseButton.innerHTML = "Pause/Resume";
pauseButton.addEventListener("click", () => {
    if (controlledRequest.isPaused) {
        controlledRequest.resume();
        console.log("Request resumed");
    } else {
        controlledRequest.pause();
        console.log("Request paused");
    }
});


// 将按钮添加到页面
document.body.appendChild(pauseButton);


// 发起请求
controlledRequest.then(data => {
    console.log("Data received: ", data);
}).catch(error => {
    console.error("Error: ", error);
});           

这个案例可以在一个网页上运行,当用户点击按钮时,请求将在暂停和恢复之间切换,最后接收到的数据将打印在控制台中。虽然实际的请求没有真正暂停(因为我们不能直接暂停一个已经发送的HTTP请求),但我们可以控制当数据返回时我们做什么,从而模拟出暂停和恢复的效果。

请注意,我们这里的bigRequest函数仅用于模拟一个需要较长时间才能完成的请求。在实际应用中,这将是一个实际的网络请求,例如使用fetch或axios等。

总结

在这篇文章中,我们讨论了JS发起的请求能否被暂停的问题,探讨了暂停的定义和请求的含义,并且介绍了如何在JS中实现假暂停的机制。虽然在网络层面,我们无法直接控制请求的暂停,但是在应用层面,我们可以通过一些巧妙的设计,实现请求的暂停功能,从而在一定程度上满足我们的业务需求。

感谢您阅读本文,如果对您有帮助,请点赞、关注和收藏。您的支持就是我继续的动力,让我们一起在前端的道路上不断前行,共同成长!

继续阅读