简体   繁体   中英

How to return only resolved Promises, ignoring errors, in typescript?

I have created the function, which should take array of Promises, and return only resolved Promises. Errors must be ignored. So, it seems sth like Promise.all but without errors. The problem is to write the function correctly in typescript. Before, using only JS, I had expected results.

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)   
        }
      })
    });
  };

Short presentation of the problem. My variables:

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))

When I have the variables and pass them as array,

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

I would expect:

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

Now I have:

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

You can show my problem in console here: Playground

I think the problem is generic type here and exactly this line:

let resolvedArray: Array<T> = []

because it indicates that the returned table must consist of the same elements as in the input. And maybe it should similar to:

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

Of course I know the line above is wrong, but maybe i should follow this idea.. Or maybe my function is not OK?

Please about any help!

This seems to be enough.

The idea is that ERRORED is a sentinel object; if a promise in the array throws, we catch that and transmute it into an ERRORED .

isNotErrored(x) is a type guard function that guarantees TypeScript that x isn't the sentinel, so the type of results.filter(isNotErrored) ends up being T[] , not (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);
}

Eg


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

logs

[8, 16]

ignoring the rejection.

EDIT:

For the original inputs

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))

this won't type-check, since not all inputs are promises, and that's IMHO the correct behavior. As discussed in the comments, bare values should be wrapped with Promise.resolve() , ie

const p2 = Promise.resolve(22)

or you could do that internally:


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);
}
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' ]

Update

Removed flatMap version to avoid confusion

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM