Dieter
Dieter

Reputation: 95

Reduce on list of promises

I have a list of jobs that should be sequentially executed. As the jobs take seconds to be finished the should run in the background. I thought that a job could be described as

interface Job {
    name: string
    execute(): Promise<boolean>
}

I would like to have a function which takes this list of jobs and execute them sequentially until the list is completed or one job fails or is rejected, so basically:

function executeUntilFailed(jobs: Job[]): Promise<boolean> 
{
    // execute first job
    // if this job
    //   - returns with true: continue with the next job
    //   - returns with false: resolve the promise with false
    //   - got rejected: reject the promise with the reason prefixed with the jobs name
    //
    // if there are no more jobs to do, resolve the promise with true
    //
    // basically it's a reduce operation with starting value of true and 
    // early stops if one job yields false or got rejected
}

I'm rather new to Javascript/Typescript and have a hard time implementing this.

Thanks, Dieter

Upvotes: 0

Views: 147

Answers (3)

trincot
trincot

Reputation: 350290

Just as an alternative, you could create a generator and then use the for await ... of syntax:

function * chainJobs(jobs) {
    for (const job of jobs) {
        yield job.execute().catch(err => new Error(`${job.name}: ${err.message}`));
    }
}

async function executeUntilFailed(jobs) {
    for await (const result of chainJobs(jobs)) {
        if (!result) return false;
        if (result instanceof Error) throw result;
    }
    return true;
}

Upvotes: 2

Dieter
Dieter

Reputation: 95

Thanks to Aluan Hadded and ehab.

I collected their solutions and have now the following code, which does exactly what I need:

    interface Job {
        name: string
        execute(): Promise<boolean>
    }


    async function executeUntilFailed(jobs: Job[]) {
        for (const job of jobs) {
            try {
                if(!await job.execute()) {
                    return false
                }
            }
            catch (err) {
                throw new Error(`${job.name}: ${err.message}`)
            }
        }
        return true
    }

and here is some example for it

    class JobImpl implements Job {
        constructor(public name: string, private value: boolean, private throwMsg: string|null = null) {}
        execute(): Promise<boolean> {
            console.log(`executing job '${this.name}'`)
            return new Promise((resolve,reject) => {
                setTimeout(()=> {
                    if(this.throwMsg!=null) { reject(this.throwMsg) }
                    else { console.log(`finished job '${this.name}' with result: ${this.value}`); resolve(this.value) }
                }, 1000)
            })
        }
    }

    const successJobs = [
        new JobImpl("a", true),
        new JobImpl("b", true),
        new JobImpl("c", true),
    ]

    const failedJobs = [
        new JobImpl("d", true),
        new JobImpl("e", false),
        new JobImpl("f", true),
    ]

    const throwingJobs = [
        new JobImpl("g", true),
        new JobImpl("g", true, "undefined problem"),
        new JobImpl("i", true),
    ]


    executeUntilFailed(successJobs)
        .then((res) => console.log("resolved", res))
        .catch((err) => console.log("rejected", err))

    executeUntilFailed(failedJobs)
        .then((res) => console.log("resolved", res))
        .catch((err) => console.log("rejected", err))

    executeUntilFailed(throwingJobs)
        .then((res) => console.log("resolved", res))
        .catch((err) => console.log("rejected", err))

<!-- end snippet -->

Upvotes: 2

ehab
ehab

Reputation: 8044

You could achieve this either with a reduce function or a for of loop, i will show an implementation in a for of

async function executeUntilFailed(jobs) {
  for (const job of jobs) {
    try {
     // notice that if a job resolved with false then it is considered a successful job
     // This is normal and a promise resolved with false should not be considered an error
      await job.execute()

      // if u want based on your description to resolve the whole promise with false if one of promises resolved with false you could do
     // const jobResult = await job.execute()
        // if (jobResult === false) {
           //  return Prmise.resolve(false)
       //  }
    } catch (err) {
      return Promise.reject(new Error(`${job.name}_${err.toString()}`))
    }
  }
  return Promise.resolve(true)
}

lets see the function in action


const successJobs = [{
    name: "a",
    execute: () => Promise.resolve(1)
  },
  {
    name: "b",
    execute: () => Promise.resolve(2),
  },
  {
    name: "c",
    execute: () => Promise.resolve(3)
  },
]

const failedJobs = [{
    name: "a",
    execute: () => Promise.resolve(1)
  },
  {
    name: "b",
    execute: () => Promise.reject(new Error("undefined problem")),
  },
  {
    name: "c",
    execute: () => Promise.resolve(3)
  },
]

async function executeUntilFailed(jobs) {
  for (const job of jobs) {
    try {
      await job.execute()
    } catch (err) {
      return Promise.reject(new Error(`${job.name}_${err.toString()}`))
    }
  }
  return Promise.resolve(true)
}


console.log(
  executeUntilFailed(successJobs)
  .then((res) => console.log("resolved", res))
  .catch((err) => console.log("rejected", err))
)


console.log(
  executeUntilFailed(failedJobs)
  .then((res) => console.log("resolved", res))
  .catch((err) => console.log("rejected", err))
)

Upvotes: 0

Related Questions