跳至主要內容

取消 Promise

Yuchen大约 2 分钟JSJSTSSnippets

取消 Promise

fetch 和 axios 都支持使用AbortControlleropen in new window的方式来取消请求。以 fetch 为例,想要取消请求,分为三步:

const url = "https://blog.yuchen.tech/";

// 1. 创建AbortController
const controller = new AbortController();
const signal = controller.signal;

// 2. 发送请求时传入AbortController的signal
fetch(url, { signal })
  .then((response) => {
    console.log(response);
  })
  .catch((error) => {
    console.log(error);
  });

// 3. 使用AbortController的abort方法来取消请求
controller.abort();
// Node 下输出
// DOMException [AbortError]: This operation was aborted
// 浏览器下输出
// DOMException: The user aborted a request.

原生的 promise 只有 pending、fulfilled 和 rejected 三种状态,是不支持取消的,要想像 fetch 和 axios 一样取消 promise, 需要在使用时对其做一些封装。主要的方法有以下 3 种:

借助 reject 方法

一个简单的例子

// 返回一个promise和abort方法
function abortPromise() {
  let _reject;

  const promise = new Promise((resolve, reject) => {
    _reject = reject;
    // 模拟异步任务
    setTimeout(() => {
      resolve("success");
    }, 5000);
  });

  return {
    promise,
    abort: () => {
      _reject({
        name: "AbortError",
        message: "The promise was aborted",
      });
    },
  };
}

const { promise, abort } = abortPromise();
promise
  .then((res) => console.log(res))
  .catch((error) => {
    console.log(error);
  });

abort(); // { name: 'AbortError', message: 'The promise was aborted' }

可以看到调用 abort 方法后 promise 被终止了,为了更加通用,需要给 abortPromise 传入一个回掉函数。

// 返回一个promise和abort方法
function abortPromise(cb) {
  let _reject;

  const promise = new Promise((resolve, reject) => {
    _reject = reject;
    cb && cb(resolve, reject);
  });

  return {
    promise,
    abort: () => {
      _reject({
        name: "AbortError",
        message: "The promise was aborted",
      });
    },
  };
}

const { promise, abort } = abortPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  }, 5000);
});
promise
  .then((res) => console.log(res))
  .catch((error) => {
    console.log(error);
  });

abort(); // { name: 'AbortError', message: 'The promise was aborted' }

这样就实现了一个最简单的取消 promise 的功能。

借助 race 方法

Promise.race()的状态与最先 resolve/reject 的那个 promise 的状态相同。我们可以用它来实现一个取消 promise 的功能。

// 返回一个promise和abort方法
function abortPromise(p) {
  let _reject;

  const promise = Promise.race([
    p,
    new Promise((resolve, reject) => {
      _reject = reject;
    }),
  ]);

  return {
    promise,
    abort: () => {
      _reject({
        name: "AbortError",
        message: "The promise was aborted",
      });
    },
  };
}

const { promise, abort } = abortPromise(
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("success");
    }, 5000);
  })
);
promise
  .then((res) => console.log(res))
  .catch((error) => {
    console.log(error);
  });

abort(); // { name: 'AbortError', message: 'The promise was aborted' }

借助 AbortController

除了上面两种方法,还可以和 fetch 和 axios 一样使用 AbortController 来取消 promise。

根据 MDN 上AbortSignalopen in new window的例子,有以下代码:

function abortPromise(p, { signal } = {}) {
  return new Promise((resolve, reject) => {
    if (signal && signal.aborted) {
      reject(signal.reason);
    }

    p.then(resolve).catch(reject);

    if (signal) {
      signal.addEventListener("abort", () => {
        reject(signal.reason);
      });
    }
  });
}

使用

let controller = new AbortController();
let signal = controller.signal;

abortPromise(
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("success");
    }, 5000);
  }),
  { signal }
)
  .then((res) => {
    console.log(res);
  })
  .catch((error) => {
    console.log(error);
  });

controller.abort(); // DOMException [AbortError]: This operation was aborted

还可以在括号里传入 reason

controller.abort({ name: "AbortError", message: "The promise was aborted" });
// { name: 'AbortError', message: 'The promise was aborted' }