如何使用 clearTimeout 功能实现 promisified setTimeout?

How to implement promisified setTimeout with clearTimeout functionality?

下面的实现会抛出一个错误(见下面的评论),如何解决这个问题?

interface PromiseWithAbort extends Promise<unknown> {
  abort: () => void
}

export const pause = (
  ms?: number,
  cb?: (...args: unknown[]) => unknown,
  ...args: unknown[]
): PromiseWithAbort => {
  let timeout

  // Error: Property 'abort' is missing in type 'Promise<unknown>'
  // but required in type 'PromiseWithAbort'.
  const promise: PromiseWithAbort = new Promise((resolve, reject) => {
    timeout = setTimeout(async () => {
      try {
        resolve(await cb?.(...args))
      } catch (error) {
        reject(error)
      }
    }, ms)
  })

  promise.abort = () => clearTimeout(timeout)

  return promise
}

问题是您分配给 promise 的承诺没有 abort 属性,但您分配的类型需要一个 promise。解决这个问题的一种简单方法是在将其分配给 promise 之前添加它。 (这也将使您摆脱 promise 上的显式类型。)

还有其他一些事情,请参阅 *** 评论:

interface PromiseWithAbort extends Promise<unknown> {
    abort: () => void
}

export const pause = (
    ms?: number,
    cb?: (...args: unknown[]) => unknown,
    ...args: unknown[]
): PromiseWithAbort => {
    let timeout: number; // *** Need the type in order to avoid implicit `any`
  
    // *** Add `abort` to the promise before assigning to `promise`
    const promise = Object.assign(
        new Promise((resolve, reject) => {
            timeout = setTimeout(async () => {
                try {
                    resolve(await cb?.(...args));
                } catch (error) {
                    reject(error);
                }
             }, ms); // *** `ms` needs a default value, you're optionally passing `undefined`
        }), {
            abort: () => clearTimeout(timeout)
        }
    );
  
    return promise;
}

On the playground

也就是说,在 cb(如果有的话)返回的承诺上使用 await 并将结果传递给 resolve 有点迂回;相反,您可以将承诺传递给 resolve,这会将您创建的承诺解析为 cb(如果有)返回的承诺:

export const pause = (
    ms?: number,
    cb?: (...args: unknown[]) => unknown,
    ...args: unknown[]
): PromiseWithAbort => {
    let timeout: number;
  
    // *** Add `abort` to the promise before assigning to `promise`
    const promise = Object.assign(
        new Promise((resolve, reject) => {
            timeout = setTimeout(() => { // *** No need for `async`
                try {
                    resolve(cb?.(...args)); // *** No need for `await`, just resolve the promise to `cb`'s promise
                } catch (error) {
                    reject(error);
                }
             }, ms);
        }), {
            abort: () => clearTimeout(timeout)
        }
    );
  
    return promise;
}

On the playground


就其价值而言,我不会将 abort 添加到承诺中,尤其是因为当您在该承诺上使用 .then.catch 或在async 函数,你从他们那里得到的承诺不会有 abort 方法。相反,您可以考虑接受 AbortSignal.

我还会删除 cb 并使 pause 成为一个纯粹的暂停功能。 cb 不必要地使它复杂化;您可以只使用 .thenawait,然后直接在您的代码中调用 cb

这是一个例子:

class CancelledError extends Error {
    constructor(msg = "Operation was cancelled") {
        super(msg);
    }
}

interface PauseOptions {
    signal?: AbortSignal;
    silent?: boolean;
}
export const pause = (
    ms: number,
    {signal, silent = false}: PauseOptions = {}
): Promise<void> => {
    return new Promise((resolve, reject) => {
        // Function we'll use if the operation is cancelled
        const cancelled = () => {
            if (!silent) {
                reject(new CancelledError());
            }
        };
        // The actual timer
        const handle = setTimeout(() => {
            if (signal?.aborted) { // It would be rare for this to happen
                cancelled();
            } else {
                resolve();
            }
        }, ms);
        // Handle cancellation
        signal?.addEventListener("abort", () => {
            clearTimeout(handle);
            cancelled();
        });
    });
};

On the playground