Dynamic sequential execution of promises

2019-02-20 03:27发布

问题:

I have a dynamic number of promises that I need to run sequentially. I understood how I can run sequentially promises but I don't succeed to make it dynamic with a number of promises that could vary.

Here is a way I found to do it statically How to resolve promises one after another? :

function waitFor(timeout) {
	return new Promise(function(resolve, reject) {
		setTimeout(function() {
			resolve(`Finished waiting ${timeout} milliseconds`);
		}, timeout);
	});
}

waitFor(1000).then(function(result) {
	$('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
    return waitFor(2000);
}).then(function(result) {
    $('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
    return waitFor(3000);
}).then(function(result) {
	$('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div id="result"></div>

I would like to do the same but instead of 3 nested promises, I would like to have any number I want. Can you help me ?

Thanks a lot!!

回答1:

There are three basic ways to achieve this task with Promises.

  1. .reduce() pattern.

function waitFor(timeout) {
	return new Promise(function(resolve, reject) {
		setTimeout(function() {
			resolve(`Finished waiting ${timeout} milliseconds`);
		}, timeout);
	});
}

var timeouts = [1000, 2000, 2000, 3000, 1000],
    sequence = tos => tos.reduce((p,c) => p.then(rp => waitFor(c))
                                           .then(rc => console.log(`${rc} @ ${new Date().getSeconds()}`)), Promise.resolve());

sequence(timeouts);

  1. The recursive pattern.

function waitFor(timeout) {
	return new Promise(function(resolve, reject) {
		setTimeout(function() {
			resolve(`Finished waiting ${timeout} milliseconds`);
		}, timeout);
	});
}

var timeouts = [1000, 2000, 2000, 3000, 1000],
    sequence = ([to,...tos]) => to !== void 0 && waitFor(to).then(v => (console.log(`${v} @ ${new Date().getSeconds()}`), sequence(tos)));

sequence(timeouts);

  1. Scan from left pattern.

The scanl pattern would sequence promises one after another but once it is completed you also have access to the interim promise resolutions. This might be useful in some cases. If you are going to construct an asynchronous tree structure lazily (branching from the nodes only when needed) you need to have access to the previous promise resolutions.

In order to achieve scanl functionality in JS, first we have to implement it.

var scanl = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc))

we feed scanl with xs which is the array of timeouts in this particular example, f which is a callback function that takes acc (the accumulator) and e (current item) and returns the new accumulator. Accumulator values (the interim promise resolutions) are mapped over the timeouts array to be accessed when needed.

function waitFor(timeout) {
	return new Promise(function(resolve, reject) {
		setTimeout(function() {
			resolve(`finished waiting ${timeout} milliseconds`);
		}, timeout);
	});
}

var timeouts = [1000, 2000, 2000, 3000, 1000],
    scanl    = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc)),
    proms    = scanl(timeouts,                                             // input array
                     (a,t,r) => a.then(v => (r = v, waitFor(t)))           // callback function
                                 .then(v => (console.log(`${r} and ${v}`),
                                             `${r} and ${v}`)),
                     Promise.resolve(`Started with 0`));                   // accumulator initial value

// Accessing the previous sub sequential resolutions
Promise.all(proms)
       .then(vs => vs.forEach(v => console.log(v)));
.as-console-wrapper {
max-height: 100% !important
}



回答2:

Make a seprate function to handle the number of iterations

function waitFor(timeout) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(`Finished waiting ${timeout} milliseconds`);
        }, timeout);
    });
}
function resultHandler(result) {
    $('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
    return waitFor(2000);
}
function repeat(promise,num){
    if(num>0)
    repeat(promise.then(resultHandler),num-1);
}

repeat(waitFor(1000),2)


回答3:

Forget I commented (when you convert a Promise to Observable or include the promise in an array, the Promise is executed). You can use a "recursive" function

  foolPromise(index: number, interval: number) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ id: index, data: new Date().getTime() % 100000 });
      }, interval);
    })
  }
  intervals: number[] = [1000, 50, 500];
  recursive(req: any, index: number) {
    req.then(res => {
      console.log(res);
      index++;
      if (index < this.intervals.length)
        this.recursive(this.foolPromise(index, this.intervals[index]), index);
    })
  }

  ngOnInit() {
    this.recursive(this.foolPromise(0, this.intervals[0]), 0)
  }


回答4:

If you don't care for serialization, you can use Promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Promise.all([promise1, promise2, promise3]).then(function(values) {
  // do something with values
}).catch(function(err) {
  // error called on first failed promise
});

Or, you can use an async function:

async function doSomething(arrayOfPromises) {
  for (const item of arrayOfPromises) {
    const result = await item;
    // now do something with result
  }
};