1

I have a class with multiple methods, each method either call async functions, or manipulating data created by other methods.

How can I make sure that calling class methods would wait for previous method finish before proceeding?

Here is a simple example that calls random methods of the class, each method takes random number of seconds to execute, I need to make sure that each called method waited for result of previous method:

class Test
{
  constructor()
  {
    //ignore the constructor, it's simplified for this example and does not play any role in the question
    const list = [...Array(~~(Math.random()*3)+3)].map(e=>~~(Math.random()*3+1));
    console.log("expected: " + list);
    list.forEach(f => this["method_" + f]());
  }
  method_1()
  {
    return new Promise(resolve => setTimeout(() => 
    {
      console.log(1);
      resolve("res 1");
    }, Math.random() * 1000 + 100));
  }
  method_2()
  {
    return new Promise(resolve => setTimeout(() => 
    {
      console.log(2);
      resolve("res 2");
    }, Math.random() * 1000 + 100));
  }
  method_3()
  {
    func3();
  }
}

new Test();

function func3()
{
  new Promise(resolve => setTimeout(() => 
  {
    console.log(3);
    resolve("res 3");
  }, Math.random() * 1000 + 100));
}

I can't use then method, because each method doesn't know which method was executed before it...or can I?

I was thinking about adding a sort of queue, where called method would be placed when called, and executed when previous promise was resolved...not sure if that would be a best approach.

vanowm
  • 9,466
  • 2
  • 21
  • 37
  • 1
    yes, you can use `.then` ... like this `let p = Promise.resolve(); list.forEach(f => p = p.then(() => this["method_" + f]()));` - but you get "neater" code using reduce - `list.reduce((p, f) => p.then(() => this["method_" + f]()), Promise.resolve());` – Jaromanda X Aug 25 '22 at 01:32
  • 2
    You really really *should* just use `for (const f of list) { await this["method_" + f](); }` [instead of `forEach`](https://stackoverflow.com/a/37576787/1048572). Yes, you could implement [such a queue](https://stackoverflow.com/a/64955723/1048572) as part of your instance state, but it'll become unwieldy since *all* your class methods become asynchronous with that. – Bergi Aug 25 '22 at 01:36
  • but you can't use await directly in the constructor :p – Jaromanda X Aug 25 '22 at 01:42
  • the constructor was just an example, I should've note that, it's not how these methods are actually executed...Each method can be executed throughout the code, there is no rhyme or reason in which sequence or when they are executed. And not all methods are returning a promise, they might only execute an async function. I'll update the example to reflect that. – vanowm Aug 25 '22 at 01:42
  • https://stackoverflow.com/q/28375619/1048572 https://stackoverflow.com/questions/39029429/ – Bergi Aug 25 '22 at 01:45
  • 2
    "*not all methods are returning a promise, they might only execute an async function*" - don't do that. An method doing something asynchronous should *always* return a promise. – Bergi Aug 25 '22 at 01:46
  • @Bergi the reason why they might not return a promise, is because they use chaining...i.e. `return fetch("blah").then(r => r.blob());` – vanowm Aug 25 '22 at 01:49
  • 2
    @vanowm Well that *does* `return` that chained promise! – Bergi Aug 25 '22 at 01:50
  • Ops, my bad, you are correct, it's resolve will contain whatever returned in last chain... anyhow, making this as synchronous, is basically what I'm after. – vanowm Aug 25 '22 at 01:56
  • Is there a reason that this could not be just a set of functions which return promises which the caller chains together? I.e, do you _need_ a certain set of functions to always be called in order? It feels like you're basically recreating a queue. I'm not a fan of the class based approach you've put as an answer because at any point the state of that class could change and it would be hard to know which function caused that change. – Dan Aug 25 '22 at 02:47
  • @Dan I'm not particularly set on using class. However, the sequence at which the methods executed is important. (this class it for icon manipulation, which combines several images loaded via async `fetch`, draw, etc. ) I'm trying to simplify the class and avoid using `then` throughout the code – vanowm Aug 25 '22 at 03:05

1 Answers1

0

Since essentially I needed make this class synchronous, using queue suggested by @Bergi works well:

class Test
{
  constructor()
  {
    this.queueList = Promise.resolve();

    //ignore the rest of the constructor, it's simplified for this example and does not play any role in the question
    const list = [...Array(~~(Math.random()*3)+3)].map(e=>~~(Math.random()*3+1));
    console.log("expected: " + list);
    list.forEach(f => this["method_" + f]());
  }
  queue(func)
  {
    return (this.queueList = this.queueList.then(func).catch(console.error));
  }
  method_1()
  {
    return this.queue(() => new Promise(resolve => setTimeout(() => 
    {
      console.log(1);
      resolve("res 1");
    }, Math.random() * 1000 + 100)));
  }
  method_2()
  {
    return this.queue(() => new Promise(resolve => setTimeout(() => 
    {
      console.log(2);
      resolve("res 2");
    }, Math.random() * 1000 + 100)));
  }
  method_3()
  {
    return this.queue(func3);
  }
}

new Test();

function func3()
{
    return new Promise(resolve => setTimeout(() => 
    {
      console.log(3);
      resolve("res 3");
    }, Math.random() * 1000 + 100));
}
vanowm
  • 9,466
  • 2
  • 21
  • 37