并行调用一个promise数组,但是可以按顺序解决它们,而不必等待其他promise得以解决 [英] Call an array of promises in parallel, but resolve them in order without waiting for other promises to resolve

查看:89
本文介绍了并行调用一个promise数组,但是可以按顺序解决它们,而不必等待其他promise得以解决的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一系列的诺言,我想并行调用,但是要同步解决.

I have an array of promises that I would like to call in parallel, but resolve synchronously.

我做了一些代码来完成所需的任务,但是,我需要创建自己的对象 QueryablePromise 来包装可以同步检查的本机 Promise 查看其已解决状态.

I made this bit of code to do the required task, however, I needed to create my own object QueryablePromise to wrap the native Promise that I can synchronously check to see it's resolved status.

有没有更好的方法可以完成不需要特殊对象的任务?

Is there any better way to achieve this task that doesn't require a special object?

请注意.我不想使用 Promise.all ,因为我不想在处理承诺的效果之前必须等待所有的承诺解决.而且我无法在代码库中使用 async 函数.

Please note. I do not want to use Promise.all as I don't want to have to wait for all promises to resolve before processing the effects of the promises. And I cannot use async functions in my code base.

const PROMISE = Symbol('PROMISE')
const tap = fn => x => (fn(x), x)

class QueryablePromise {
  resolved = false
  rejected = false
  fulfilled = false
  constructor(fn) {
    this[PROMISE] = new Promise(fn)
      .then(tap(() => {
        this.fulfilled = true
        this.resolved = true
      }))
      .catch(x => {
        this.fulfilled = true
        this.rejected = true
        throw x
      })
  }
  then(fn) {
    this[PROMISE].then(fn)
    return this
  }
  catch(fn) {
    this[PROMISE].catch(fn)
    return this
  }
  static resolve(x) {
    return new QueryablePromise((res) => res(x))
  }
  static reject(x) {
    return new QueryablePromise((_, rej) => rej(x))
  }
}

/**
 * parallelPromiseSynchronousResolve
 *
 * Call array of promises in parallel but resolve them in order
 *
 * @param  {Array<QueryablePromise>}  promises
 * @praram {Array<fn>|fn}  array of resolver function or single resolve function
 */
function parallelPromiseSynchronousResolve(promises, resolver) {
  let lastResolvedIndex = 0
  const resolvePromises = (promise, i) => {
    promise.then(tap(x => {
      // loop through all the promises starting at the lastResolvedIndex
      for (; lastResolvedIndex < promises.length; lastResolvedIndex++) {
        // if promise at the current index isn't resolved break the loop
        if (!promises[lastResolvedIndex].resolved) {
          break
        }
        // resolve the promise with the correct resolve function
        promises[lastResolvedIndex].then(
          Array.isArray(resolver)
            ? resolver[lastResolvedIndex]
            : resolver
        )
      }
    }))
  }
  
  promises.forEach(resolvePromises)
}

const timedPromise = (delay, label) => 
  new QueryablePromise(res => 
    setTimeout(() => {
      console.log(label)
      res(label)
    }, delay)
  )

parallelPromiseSynchronousResolve([
  timedPromise(20, 'called first promise'),
  timedPromise(60, 'called second promise'),
  timedPromise(40, 'called third promise'),
], [
  x => console.log('resolved first promise'),
  x => console.log('resolved second promise'),
  x => console.log('resolved third promise'),
])

<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>

为任何帮助加油.

推荐答案

使用

Using a for await...of loop, you can do this quite nicely if you already have the array of promises:

const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); });
const range = (length, mapFn) => Array.from({ length }, (_, index) => mapFn(index));

(async () => {
  const promises = range(5, index => {
    const ms = Math.round(Math.random() * 5000);
    return delay(ms).then(() => ({ ms, index }));
  });

  const start = Date.now();

  for await (const { ms, index } of promises) {
    console.log(`index ${index} resolved at ${ms}, consumed at ${Date.now() - start}`);
  }
})();

由于您不能使用异步函数,因此可以通过使用 await ... of 的效果.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce"rel =" nofollow noreferrer> Array.prototype.reduce() ,并同步安排每个链的回调:

Since you can't use asynchronous functions, you can mimic the effect of for await...of by chaining the promises together using Array.prototype.reduce(), and synchronously scheduling a callback for each chain:

const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); });
const range = (length, mapFn) => Array.from({ length }, (_, index) => mapFn(index));

const asyncForEach = (array, cb) => array.reduce(
  (chain, promise, index) => chain.then(
    () => promise
  ).then(
    value => cb(value, index)
  ),
  Promise.resolve()
);

const promises = range(5, index => {
  const ms = Math.round(Math.random() * 5000);
  return delay(ms).then(() => ms);
});

const start = Date.now();

asyncForEach(promises, (ms, index) => {
  console.log(`index ${index} resolved at ${ms}, consumed at ${Date.now() - start}`);
});

由于承诺被声明为并行实例化,因此我假设除通过 asyncForEach()构造的任何可能的脆弱链之外,任何单个承诺的错误都不会传播到其他承诺.以上).

Since the promises were stated to be instantiated in parallel, I'll assume that errors on any individual promise will not propagate to other promises except through any potentially brittle chains constructed via asyncForEach() (like above).

但是,当在 asyncForEach()中将它们链接在一起时,我们也希望避免在Promise之间交叉传播错误.这是一种可靠地安排错误回调的方法,其中错误只能从原始的Promise传播:

But we also want to avoid cross-propagating errors between promises when chaining them together in asyncForEach(). Here's a way to schedule error callbacks robustly where errors can only propagate from the original promises:

const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); });
const maybe = p => p.then(v => Math.random() < 0.5 ? Promise.reject(v) : v);
const range = (length, mapFn) => Array.from({ length }, (_, index) => mapFn(index));

const asyncForEach = (array, fulfilled, rejected = () => {}) => array.reduce(
  (chain, promise, index) => {
    promise.catch(() => {}); // catch early rejection until handled below by chain
    return chain.then(
      () => promise,
      () => promise // catch rejected chain and settle with promise at index
    ).then(
      value => fulfilled(value, index),
      error => rejected(error, index)
    );
  },
  Promise.resolve()
);

const promises = range(5, index => {
  const ms = Math.round(Math.random() * 5000);
  return maybe(delay(ms).then(() => ms)); // promises can fulfill or reject
});

const start = Date.now();

const settled = state => (ms, index) => {
  console.log(`index ${index} ${state}ed at ${ms}, consumed at ${Date.now() - start}`);
};

asyncForEach(
  promises,
  settled('fulfill'),
  settled('reject') // indexed callback for rejected state
);

这里唯一需要注意的是,传递给 asyncForEach()的回调中引发的任何错误都将被链中的错误处理吞噬,除了最后一个索引的回调中引发的错误之外的数组.

The only caveat to note here is that any errors thrown in the callbacks passed to asyncForEach() will get swallowed by the error handling in the chain except for errors thrown within the callbacks on the last index of the array.

这篇关于并行调用一个promise数组,但是可以按顺序解决它们,而不必等待其他promise得以解决的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆