【问题标题】:How to return only resolved Promises, ignoring errors, in typescript?如何在打字稿中只返回已解决的承诺,忽略错误?
【发布时间】:2020-09-10 06:48:42
【问题描述】:

我已经创建了函数,它应该接受 Promises 数组,并且只返回已解决的 Promises。必须忽略错误。所以,它看起来像 Promise.all 但没有错误。问题是在打字稿中正确编写函数。之前,只使用 JS,我有预期的结果。

function promiseIgnoreErrors<T> (arrayOfPromise: Array<T>)  {
    const isArrayEmpty =
      Array.isArray(arrayOfPromise) && arrayOfPromise.length === 0;
  
    return new Promise((resolve, reject) => {
      if (isArrayEmpty) reject(new Error('Forever pending array'));
  
      let resolvedArray: Array<T> = []
      
      let resolvedCounter: number = 0;
  
      arrayOfPromise.forEach((promise, index) => {
        if (promise instanceof Promise) {
          promise
            .then((data) => {
              resolvedArray[index] = data
              resolvedCounter++;      
              if (arrayOfPromise.length === resolvedCounter) resolve(resolvedArray)   
            })
            .catch(() => {
              resolvedCounter++;
              if (arrayOfPromise.length === resolvedCounter) resolve(resolvedArray)
            })
        } else {
          resolvedArray[index] = promise
          resolvedCounter++;
          if (arrayOfPromise.length === resolvedCounter) resolve(resolvedArray)   
        }
      })
    });
  };

问题的简短介绍。我的变量:

const p1 = new Promise((resolve) => setTimeout(() => resolve('JS'), 500))
const p2 = 22
const p3 = new Promise((reject) => setTimeout(() => reject(new Error('Oops')), 100))
const p4 = new Promise((resolve) => setTimeout(() => resolve('TS'), 200))

当我有变量并将它们作为数组传递时,

promiseIgnoreErrors([p1, p2, p3, Promise.reject(10), p4])

我希望:

Promise {<pending>}
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(3)
// ['JS', 22, 'TS']

现在我有:

Promise {<pending>}
    [[PromiseState]]: "fulfilled"
    [[PromiseResult]]: Array(5)
    // ['JS', 22, Error: 'Oops, ?, 'TS']

您可以在控制台中显示我的问题: Playground

我认为这里的问题是泛型类型,正是这一行:

let resolvedArray: Array<T> = []

因为它表明返回的表必须包含与输入中相同的元素。也许它应该类似于:

let resolvedArray: Array<K> extends T = []

我当然知道上面这行是错的,但也许我应该遵循这个想法.. 或者我的功能不正常?

请任何帮助!

【问题讨论】:

  • 类型对运行时发生的事情没有影响,如果您得到错误的输出,则 code 是错误的。
  • reject(new Error('Forever pending array')); 在我看来确实是个错误。但说真的,你为什么还要自己写这个而不是使用Promise.allSettled
  • @jonrsharpe 我明白了。无论如何,谢谢!
  • 代码中的一个问题是new Promise((reject) =&gt; setTimeout(() =&gt; reject(new Error('Oops')), 100)) 完全等同于new Promise((resolve) =&gt; setTimeout(() =&gt; resolve(new Error('Oops')), 100))。您可能打算使用new Promise((resolve, reject) =&gt; …)
  • @Bergi 我注意到了。当我测试我的代码时出现问题。然后当我有new Promise((resolve, reject) =&gt; setTimeout(() =&gt; reject(new Error('Oops')), 100)) 时,打字稿对我大喊大叫,因为我不使用“解决”.. 我的测试自动失败。

标签: javascript typescript promise typescript-generics


【解决方案1】:

这似乎已经足够了。

这个想法是ERRORED是一个哨兵对象;如果数组中的 promise 抛出,我们会捕获它并将其转换为 ERRORED

isNotErrored(x) 是一个类型保护函数,它保证 TypeScript 的 x 不是哨兵,所以 results.filter(isNotErrored) 的类型最终是 T[],而不是 (T | '__ERRORED__')[]

const ERRORED = '__ERRORED__';
type ErroredType = typeof ERRORED;

function isNotErrored<T>(x: T | ErroredType): x is T {
  return x !== ERRORED;
}

async function promiseIgnoreErrors<T>(promises: Array<Promise<T>>): Promise<Array<T>> {
  const results = await Promise.all(promises.map(p => p.catch(() => ERRORED as ErroredType)));
  return results.filter(isNotErrored);
}

例如


promiseIgnoreErrors([Promise.resolve(8), Promise.reject("oh no"), Promise.resolve(16)]).then(r => {
  console.log(r);
});

日志

[8, 16]

忽略拒绝。

编辑:

对于原始输入

const p1 = new Promise((resolve) => setTimeout(() => resolve('JS'), 500))
const p2 = 22
const p3 = new Promise((reject) => setTimeout(() => reject(new Error('Oops')), 100))
const p4 = new Promise((resolve) => setTimeout(() => resolve('TS'), 200))

这不会进行类型检查,因为并非所有输入都是承诺,恕我直言,这是正确的行为。正如 cmets 中所讨论的,裸值应该用 Promise.resolve() 包装,即

const p2 = Promise.resolve(22)

或者你可以在内部这样做:


function isPromiseLike<T>(x: T | Promise<T>): x is Promise<T> {
  return (x as any).then;  // Checks whether the object has a `then`; should be mostly enough.
}

async function promiseIgnoreErrors2<T>(values: Array<T | Promise<T>>): Promise<Array<T>> {
  const promises = values.map((v) => isPromiseLike(v) ? v : Promise.resolve(v));
  const results = await Promise.all(promises.map(p => p.catch(() => ERRORED as ErroredType)));
  return results.filter(isNotErrored);
}

【讨论】:

  • 我建议在这里使用具有唯一标识的符号或对象,而不是字符串文字。
  • @karolina-szlenk 在你的案例中做了什么或没做什么?
  • @Bergi 符号是否具有唯一的类型标识,可用于 type ErroredType 之类的东西?
  • @AKX 不知道。让 typescript 接受工作代码对我来说总是事后的想法,让它正常工作是第一要务……
  • @karolina-szlenk 我在 Typescript 操场上测试了这个;似乎工作正常。
【解决方案2】:
async function promiseIgnoreErrors<T>(arrayOfPromise: Array<T>) {
  const promises = await Promise.all(arrayOfPromise.map((p) => promisify(p)))
  return promises.filter((v) => v != null)
}

const promisify = <T>(value: T) =>
  Promise.resolve(value)
    .then((v) => (v instanceof Error ? null : v))
    .catch(() => null)


const r = promiseIgnoreErrors([p1, p2, p3, p4, p5])

console.log(r)                // Promise { <pending> }
r.then((x) => console.log(x)) // [ 'JS', 22, 'TS' ]

更新

删除 flatMap 版本以避免混淆

【讨论】:

  • 这里真的没有充分的理由使用flatMap 而不是filter
  • 我有两种变体,flatMap 会一次完成,过滤器需要两次处理
  • 呃,不是吗?第二遍从哪里来?
  • 哦,你是对的,我以为我摆脱了地图,但显然我没有;)
  • 这里希望null 不是任何承诺的有效结果......
猜你喜欢
  • 2018-12-28
  • 2018-01-21
  • 2021-05-25
  • 2018-09-29
  • 2017-06-02
  • 2013-05-15
  • 2019-09-08
  • 2019-10-15
  • 1970-01-01
相关资源
最近更新 更多