深入理解 fetch AbortController 的命令模式

62 min read

HTML 结构

一个简单的 HTML 页面,其中包含一个按钮用于发起请求和一个按钮用于取消请求:

<button id="fetchButton">发起请求</button>
<button id="cancelButton">取消请求</button>
<div id="result"></div>

JavaScript 实现

在 JavaScript 中,我们将创建一个 AbortController 实例,并使用此实例的 signal 属性来控制 fetch 请求。

同时,我们还将实现一个按钮点击事件处理器,用于发起和取消请求。

// 获取 HTML 元素
const fetchButton = document.getElementById('fetchButton');
const cancelButton = document.getElementById('cancelButton');
const resultDiv = document.getElementById('result');

// 创建 AbortController 实例
const controller = new AbortController();

// 模拟一个长时间运行的请求
fetchButton.addEventListener('click', async () => {
  resultDiv.textContent = '正在加载...';

  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', { signal: controller.signal });
    const data = await response.json();
    resultDiv.textContent = JSON.stringify(data);
  } catch (error) {
    if (error.name === 'AbortError') {
      resultDiv.textContent = '请求被取消';
    } else {
      resultDiv.textContent = '发生错误';
    }
  }
});

// 取消请求
cancelButton.addEventListener('click', () => {
  controller.abort();
});

示例解释

在此示例中:

  • 发起请求: 用户点击“发起请求”按钮时,会向一个 API 发送一个 fetch 请求。请求的控制信号来自于 AbortController 实例。

  • 取消请求: 如果用户在请求完成之前点击“取消请求”按钮,AbortControllerabort 方法将被调用,正在进行的 fetch 请求会被取消。

  • 错误处理: 如果请求被取消,会捕获到一个 AbortError,并相应地更新界面上的信息。

这个例子演示了命令模式在现代前端开发中的一个具体应用。通过将请求的控制逻辑(命令)封装在 AbortController 对象中,您可以方便地管理和取消异步 HTTP 请求。这种模式在处理复杂的用户界面交互和网络通信时尤为有用。

案例:简单的命令模式实现

假设我们有一个简单的文本编辑器应用,它可以执行撤销(undo)和重做(redo)操作。我们将使用命令模式来实现这些功能。

Step 1: 定义命令接口

首先,我们定义一个命令(Command)接口,它声明了执行命令的方法:

class Command {
  execute() {}
  undo() {}
}

Step 2: 创建具体的命令

然后,我们创建具体的命令类来实现这个接口。例如,一个添加文本的命令:

class AddTextCommand extends Command {
  constructor(receiver, text) {
    super();
    this.receiver = receiver;
    this.text = text;
    this.previousText = '';
  }

  execute() {
    this.previousText = this.receiver.getText();
    this.receiver.addText(this.text);
  }

  undo() {
    this.receiver.setText(this.previousText);
  }
}

class TextReceiver {
  constructor() {
    this.text = '';
  }

  getText() {
    return this.text;
  }

  setText(text) {
    this.text = text;
  }

  addText(newText) {
    this.text += newText;
  }
}

Step 3: 使用命令

最后,我们使用这些命令来操作文本编辑器:

// 文本编辑器实例
const editor = new TextReceiver();

// 创建命令
const addTextCommand = new AddTextCommand(editor, "Hello, Command Pattern!");

// 执行命令
addTextCommand.execute();
console.log(editor.getText()); // 输出: Hello, Command Pattern!

// 撤销命令
addTextCommand.undo();
console.log(editor.getText()); // 输出: (空字符串)

Step 4: 命令调用者

通常,命令模式还包括一个调用者(Invoker)角色,它知道如何执行命令,但不知道命令的具体实现。在实际应用中,这可能是一个按钮或菜单项。

class Invoker {
  constructor() {
    this.history = [];
  }

  executeCommand(command) {
    this.history.push(command);
    command.execute();
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
    }
  }
}

// 使用 Invoker
const invoker = new Invoker();
invoker.executeCommand(addTextCommand);
invoker.undo();

总结

在这个示例中,我们创建了一个简单的文本编辑器,它使用命令模式来实现添加文本的操作以及撤销这些操作。Command 类定义了执行和撤销命令的接口,AddTextCommand 类实现了这些接口来具体操作文本。Invoker 类管理命令的执行和撤销,它代表了一个更高层次的操作,如用户界面的按钮或菜单。

这种模式使得命令的发送者和接收者解耦,提高了代码的灵活性和可扩展性。