1

I am listening to user events and some of these depend on the completion of others. Dealing with these events is an asynchronous task, so I use promises.

Let's say I've got this:

A
  -> B
       -> C
       -> ...
       -> D
            -> E
            -> ...
            -> F

My initial approach was to save a promise for A and attach the handling of B using the then method of A's promise. The same for B and C ... D and D and E ... F.

But these events can happen almost in the same time, and B can happen before A. Since I cannot listen to a promise that not exists yet... How would you resolve this?

My idea is to define an object for each level (A, B, D), where I can attach a promise and the handling of future events. When attaching a promise, I would iterate the handling of future events and set them to the promise's then. When attaching a future event, I would look if there is a promise, and attach that to it's then or save that into an array or something.

Do you know an existing solution that already solves this issue or shall I follow with my idea?

Thank you.


Further clarification as requested:

I'm listening to multimedia events in the client side that are forwarded via websocket (using socket.io) to the server. The order of the events is indeed first A, then B, etc. But since some of them happen almost at the same time, they can be processed out of order.

Example code

let promiseForA, promiseForB;

socket.on('A', data => {
  promiseForA = asynchronousTaskForA();
});

socket.on('B', data => {
  // What if promiseForA has not yet been defined?
  promiseForA.then(neededValue => {
    asynchronousTaskForB(neededValue);
  });
});

socket.on('C', data => {
  // What if promiseForB has not yet been defined?
  promiseForB.then(neededValue => {
    asynchronousTaskForC(neededValue);
  });
});

socket.on('D', data => {
  // What if promiseForB has not yet been defined?
  promiseForB.then(neededValue => {
    asynchronousTaskForD(neededValue);
  });
});

function asynchronousTaskForA() {
  // something
  resolve('something needed by B');
}

function asynchronousTaskForB(value) {
  // something with value
  resolve('something needed by C ... D');
}

function asynchronousTaskForC(value) {
  // something with value
  resolve();
}

function asynchronousTaskForD(value) {
  // something with value
  resolve('something needed by E ... F');
}

My idea

So... it works. It may be an anti-pattern, wrong or insane, but... I'd like to know of a better alternative.

let PromiseWaiter = function() {
    let promise = null;
    let thens = [];

    let self = this;

    this.setPromise = function (p) {
        promise = p;
        thens.forEach(t => {
            p.then(t);
        });
    };

    this.then = function(t) {
        if (promise === null) {
            thens.push(t);
        } else {
            promise.then(t);
        }
        return self;
    };

    this.reset = function() {
        promise = null;
        thens = [];
    };
};

module.exports = PromiseWaiter;

Using it:

let waitForA = new PromiseWaiter();
let waitForB = new PromiseWaiter();
let waitForD = new PromiseWaiter();

socket.on('A', data => {
  waitForA.setPromise(asynchronousTaskForA());
});

socket.on('B', data => {
  waitForA.then(neededValue => {
    waitForB.setPromise(asynchronousTaskForB(neededValue));
  });
});

socket.on('C', data => {
  waitForB.then(neededValue => {
    asynchronousTaskForC(neededValue);
  });
});

socket.on('D', data => {
  waitForB.then(neededValue => {
    waitForD.setPromise(asynchronousTaskForD(neededValue));
  });
});

// Note: I am confused why these functions did not return a Promise before
// They have always done that.
function asynchronousTaskForA() {
  return new Promise((resolve, reject) => {
    // something
    resolve('something needed by B');
  });
}

function asynchronousTaskForB(value) {
  return new Promise((resolve, reject) => {
    // something with value
    resolve('something needed by C ... D');
  });
}

function asynchronousTaskForC(value) {
  return new Promise((resolve, reject) => {
    // something with value
    resolve();
  });
}

function asynchronousTaskForD(value) {
  return new Promise((resolve, reject) => {
    // something with value
    resolve('something needed by E ... F');
  });
}

Thank you!

Community
  • 1
  • 1
nerestaren
  • 156
  • 1
  • 12
  • Could you create a minimal example using code and point out at which place you have a problem to bringt multiple promises together? Because you could always chain Promises together using e.g. `return A .then(() => B).then( ... )` or `Promise.all`. – t.niese Jun 15 '17 at 10:55
  • Do you mean that B is completely independent from A and can happen in parallell but you only want to proceed with C when both A and B are complete? If so, wrap both A and B in their own promise and wrap those promises with `Promise.all()` – Lennholm Jun 15 '17 at 10:56
  • I don't get your diagram. It seems to imply that B depends on A, but then you say that B can happen before A. How would that be possible? – Bergi Jun 15 '17 at 10:57
  • "*define an object for each level, where I can attach […] the handling of future events.*" - you are exactly describing a promise here. Don't roll your own solution for this. – Bergi Jun 15 '17 at 10:59
  • I don't get what you mean by "attaching". One can attach a *handler*, a callback *function*, but not an event or a promise. You *fire* an event, and attach callbacks *to* a promise. – Bergi Jun 15 '17 at 11:00
  • Promises are rarely, if ever, suitable for handling events – Jaromanda X Jun 15 '17 at 11:03
  • 1
    Possible duplicate of [Promises for promises that are yet to be created](https://stackoverflow.com/a/37426491/1048572)? – Bergi Jun 15 '17 at 11:04
  • What processing are you going to do with the events? You say the order of the events is strictly defined, does that also mean that ever event will happen exactly once? Where do promises come into play? And where are dependencies between what events? It would help if you could post your actual code, not abstract event names. – Bergi Jun 15 '17 at 11:19
  • Hey! I've updated the question with some code. I hope it is more clear now. Thank you. – nerestaren Jun 15 '17 at 11:21

2 Answers2

2

What if promiseForA has not yet been defined?

Just don't assign to it asynchronously. Create it immediately - make a promise for it. Oh, a promise for a promise is just a promise.

const A = new Promise(resolve => socket.on('A', resolve));
const B = new Promise(resolve => socket.on('B', resolve));
const C = new Promise(resolve => socket.on('C', resolve));
const D = new Promise(resolve => socket.on('D', resolve));

const afterA = A.then(asynchronousTaskForA);
const afterB = Promise.all([afterA, B]).then(asynchronousTaskForB);
const afterC = Promise.all([afterB, C]).then(asynchronousTaskForC);
const afterD = Promise.all([afterB, D]).then(asynchronousTaskForD);

My idea may be an anti-pattern, wrong or insane, but... it works.

Yeah. The problems I see with it is that it looks very much like a promise (or deferred), but isn't one:

  • setPromise is just resolve
  • then does register callbacks, but doesn't return a promise that waits for the callback result, so is not chainable
  • reset is not possible with promises1, but I'm not sure whether you really need this.

As I said in the comments, better don't roll your own solution but just use Promise. Of course you might write a helper function socket.getPromiseForNext('A') or so.

1: You can however use an implementation like Creed that supports cancellation. I suspect just creating a new instance that waits for the next event should suffice though.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I've been reading the possible duplicate you stated in the comments, and that sounded well. This does as well. Would you mind detailing a little more what kind of magic is happening here? – nerestaren Jun 15 '17 at 11:26
  • 1
    One caveat - Make sure you have exit / fail conditions in place so the function doesn't hang permanently waiting for promises to return that re never going to be hit. If your only function exit case is inside a promise waiting for promise C, but promise C only gets called after promise B is returned, and promise B never returns because promise A fails, you need to handle all those cases. – Jake T. Jun 15 '17 at 21:21
  • @Bergi aaah! I see you changed things there :p (`A` for `afterA`...) -- Now I can understand it. Yes, I agree with you: this is the way it should be done. Thank you! :D – nerestaren Jun 16 '17 at 09:23
0

You can create a promise in the return statement of then callback. Example:

promiseA
  .then( data => {
     return new Promise((resolve, reject) => {
       // do some job
     });
  })

As far as you are using node, you can use async/await approach:

async function startFlow() {
  // here you sure create promise, instead of promise A
  const res1 = await promiseA;

  // here you sure create promise, instead of promise B
  const res2 = await promiseB;

  // etc...
}

// Start your functions execution
startFlow();
Lazyexpert
  • 3,106
  • 1
  • 19
  • 33