Let’s check this badly written example first. The sequence below will run each step asynchronously.

// y = 2 * (x² + 5)
const s1 = [
  x => Promise.resolve(x * x),
  x => Promise.resolve(x + 5),
  x => Promise.resolve(x * 2),
]

function runSequence(sequence, data) {
  return sequence.reduce((previous, task) => {
    return Promise.resolve(previous).then(task)
  }, data)
}

await runSequence(s1, 5)

In this example, we want the result of the previous stage to get passed on as input to the following stage. The runSequence helper function connects these tasks together.

What if we need the initial data in a later stage? to do that, we need to add a transformer function to runSequence. Let’s change the previous example problem so that we’ll need the initial value in a later stage.

// y = 2 * (x² + 5) + x
const s2 = [
  x => Promise.resolve(x * x),
  x => Promise.resolve(x + 5),
  x => Promise.resolve(x * 2),
  v => Promise.resolve(v.prev + v.data),
]

function runSequence(sequence, data, transformer) {
  return sequence.reduce((prev, task, idx) => {
    return Promise.resolve(prev)
      .then(prevVal => transformer(idx, prevVal, data))
      .then(task)
  }, data)
}

await runSequence(s2, 5, function (stage, prev, data) {
  switch (stage) {
    case 3:
      return { prev, data }
    default:
      return prev
  }
})

We can take it a step further and give the transformer an array of all results up to the current executing stage.

// y = 2 * (x² + 5) + x²
const s3 = [
  x => Promise.resolve(x * x),
  x => Promise.resolve(x + 5),
  x => Promise.resolve(x * 2),
  v => Promise.resolve(v.prev + v.sqrd),
]

function runSequence(sequence, data, transformer) {
  const results = []
  return sequence.reduce((prev, task, idx) => {
    return Promise.resolve(prev)
      .then(prevVal => transformer(idx, prevVal, data, results))
      .then(task)
      .then(res => {
        results.push(res)
        return res
      })
  }, data)
}

await runSequence(s3, 5, function (stage, prev, data, results) {
  switch (stage) {
    case 3:
      // reuse x² from result of stage 1
      return { prev, sqrd: results[0] }
    default:
      return prev
  }
})

If you're using RxJS, this can be done using both concat and defer.

Observable.of()
    .concat( Observable.defer(() => asyncTask()))
    .concat( Observable.defer(() => asyncTask()))