Skip to content

如何终止 promise

新建文件 buildCancelableTask.ts:

ts
type Result<T> = {
  data: T | null;
  cancelled: boolean;
  error: null | Error;
};

const canceledError = new Error("CanceledError");

const buildCancelableTask = <T>(asyncFn: () => Promise<T>) => {
  let rejected = false;
  const { promise, resolve } = Promise.withResolvers<Result<T>>();

  return {
    run: () => {
      if (!rejected) {
        asyncFn()
          .then((data) => resolve({ data, cancelled: false, error: null }))
          .catch((error: Error) => {
            const cancelled = error === canceledError;
            resolve({ data: null, cancelled, error: cancelled ? null : error });
          });
      }
      return promise;
    },
    cancel: () => {
      rejected = true;
      resolve({ data: null, cancelled: true, error: canceledError });
    }
  };
};

export default buildCancelableTask;

Vue3 封装成 useSequentialAsyncFn.ts:

ts
import buildCancelableTask from "../ts/buildCancelableTask";

export default function useSequentialAsyncFn<Args extends unknown[], Data>(
  asyncFn: (...args: Args) => Promise<Data>
) {
  let ret: ReturnType<typeof buildCancelableTask>;
  return () => {
    ret && ret.cancel();
    ret = buildCancelableTask(asyncFn);
    return ret.run();
  };
}

使用

vue
<script setup lang="ts">
import useSequentialAsyncFn from "./hook/useSequentialAsyncFn";

const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));

const run = useSequentialAsyncFn(async () => {
  await sleep(2 * 1000);
  return "Hello";
});

const onClick = async () => {
  const { cancelled, data } = await run();
  console.log("cancelled: ", cancelled);
  console.log("data: ", data);
};
</script>

<template>
  <div @click="onClick"></div>
</template>

另外 promise-withresolvers.ts 可以封装成一个 polyfills:

ts
interface PromiseWithResolvers<T> {
  promise: Promise<T>;
  resolve: (value: T | PromiseLike<T>) => void;
  reject: (reason?: any) => void;
}

if (!Promise.withResolvers) {
  Promise.withResolvers = function <T>(): PromiseWithResolvers<T> {
    let resolve!: (value: T | PromiseLike<T>) => void;
    let reject!: (reason?: any) => void;
    const promise = new Promise<T>((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

// 为了让 TypeScript 认识这个新方法,我们需要扩展 Promise 的类型定义
declare global {
  interface PromiseConstructor {
    withResolvers<T>(): PromiseWithResolvers<T>;
  }
}

export {}; // 使这个文件成为一个模块