1

I am trying to create a so-called "promise queue". The idea is that, so long as there are promises left to be resolved, we can keep adding more promises to the queue.

I am challenging myself to implement this promise queue without using async/await syntax, as I would like to see if it is even possible to do so efficiently without this syntax.

The method I attempted is to use Promise.all but pass a custom iterable object, which will keep yielding new promises if they come in on time. The problem is, Promise.all doesn't lazily iterate the iterable, but rather it iterates the promises all at once at the very beginning.

Any other ideas on how to implement a promise queue efficiently without async/await syntax?

class PromiseQueue {
    constructor() {
        this._queue = new Set();
    }

    add(promise) {
        this._queue.add(promise);
    }

    all() {
        let self = this;
        let alreadyIterated = false;

        return Promise.all({
            [Symbol.iterator]: function*() {
                if (alreadyIterated) {
                    console.error("already iterated");
                    throw new Error("already iterated");
                }

                alreadyIterated = true;

                while (true) {
                    let { value: promise, done } = self._queue.values().next();
                    
                    if (done) {
                        return;
                    }
                    
                    self._queue.delete(promise);
                    console.log("deleting");

                    yield promise;
                }
            }
        });
    }
}

let createPromise = () => {
    let resolve, reject;
    let promise = new Promise((resolve_, reject_) => {
        resolve = resolve_;
        reject = reject_;
    });

    return [promise, resolve, reject];
}

let timeout = (ms) => {
    let [promise, resolve, _reject] = createPromise();
    setTimeout(resolve, ms);
    return promise;
};

let queue = new PromiseQueue();
queue.add(timeout(1000));

setTimeout(() => {
    queue.add(timeout(1000));   
    queue.add(timeout(1000));
}, 500);

let result = await queue.all();
console.log(result);
David Callanan
  • 5,601
  • 7
  • 63
  • 105

2 Answers2

1

You can do something like this:


all() {
  var iterator = this._queue.values();
  function iterate() {
    let v = iterator.next();
    if (!v.done) {
      return v.value.then(iterate);
    }
  }
  return Promise.resolve(iterate());
}
Steve
  • 10,435
  • 15
  • 21
0

Here is updated PromiseQueue implementation inspired from the other answer.

class PromiseQueue {
    constructor() {
        this._queue = [];
    }

    add(promise) {
        this._queue.push(promise);
    }

    all() {
        return this._all([]);
    }

    _all(results) {
        let all = Promise.all(this._queue);
        this._queue = [];

        return all.then((newResults) => {
            results.push(...newResults);

            if (this._queue.length > 0) {
                return this._all(results);
            }

            return results;
        });
    }
}
David Callanan
  • 5,601
  • 7
  • 63
  • 105