nerestaren
nerestaren

Reputation: 166

Waiting for a promise before it exists

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!

Upvotes: 1

Views: 554

Answers (2)

Bergi
Bergi

Reputation: 664528

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.

Upvotes: 2

Lazyexpert
Lazyexpert

Reputation: 3154

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

Upvotes: 0

Related Questions