How to write simple, understandable, easily maintainable code that runs several asynchronous functions in javascript / jQuery sequentially? (when one works, the other should run)

The following example illustrates my question:

function f1(){ setTimeout( function(){ console.log(1); }, 30); } function f2(){ setTimeout( function(){ console.log(2); }, 20); } function f3(){ setTimeout( function(){ console.log(3); }, 10); } f1(); f2(); f3(); 

at the output of 3 2 1 how to do something would give out 1 2 3?

Preferably without callbacks - because if you need to run more than two functions in succession, it is already hard to read. Much suggests that a solution is possible using the $ .Deferred object, but has not yet seen a reasonable option.

A similar question was asked more than once, but for some reason I could not find the answer that would suit me.

  • one
    Write the function setSyncTimeout, which will loop the check of the function launch time and the current time. And in your example, the launch time of setTimeout should be 30, 20 + 30, 20 + 30 + 10 - ilyaplot
  • one
  • If you are given an exhaustive answer, mark it as correct (a daw opposite the selected answer). - Nicolas Chabanovsky
  • @NicolasChabanovsky put a daw, but for some reason it disappears again and the second time it can not be put at once ... - Serafim

3 answers 3

If you want to use Promises , then first you need to modify your functions so that they return to Promises . For example, the first of your functions will be:

 function f1() { return new Promise(function(resolve){ setTimeout(function() { console.log(1); resolve(); }, 30); }); } 

The remaining functions are converted in the same way.

You now have three functions ( f1 , f2 , f3 ) that return Promises and you want to execute them sequentially. If you are not using libraries like Bluebird, then you will have to implement the Promise call queue manually. This is not as difficult as it seems:

 // Аргумент "deeds" - это массив функций, которые должны выполняться // последовательно. При этом, каждая функция должна возвращать // Обещание (Promise). var seqRunner = function(deeds) { return deeds.reduce(function(p, deed) { return p.then(function() { // Выполняем следующую функцию только после того, как отработала // предыдущая. return deed(); }); }, Promise.resolve()); // Инициализируем очередь выполнения. } 

And you need to use this queue like this:

 seqRunner([f1, f2, f3]).then(function() { console.log('Done!'); }); 

And here is the JSFiddle with a working example .


Comment:

If you have a previously known, a small number of functions, then you can generally do without the seqRunner function and link the functions manually:

 f1().then(function() { return f2(); }).then(function() { return f3(); }).then(function() { console.log('Done!'); }); 
  • if I'm not mistaken, you can not screw up anonymous functions and get by with direct transfer: f1().then(f2).then(f3).then(...) - Grundy
  • Can. But it all depends on the arguments taken (and returned through Promises) by the functions f1 , f2 , f3 . In this particular case, no wrappers are really needed. - Dmitriy Simushev
  • @DmitriySimushev Thank you! This is the solution that I wanted to know :) Tell me: what framework would you use to make it even easier to implement? (still looks a bit complicated ...) By the way, you mentioned the Bluebird library - can you use it too? - Serafim
  • @Serafim, as far as I remember, there is no such functionality in bluebird. And about the bulkiness, if desired, the code can be reduced. Actually, I do not think that for this you need to pull up some kind of heavy framework. If desired, you can pull up a small library from npm that implements only an analogue of the seqRunner function from the response. I will not give you a specific library. not using. - Dmitriy Simushev
  • @DmitriySimushev if I may allow, I will clarify some points in which I am not quite sure: 1. Promise this to Deferred without the resolve and reject methods, which in this case are not needed because run once when creating an object. 2. The function which is initialized by Promise is launched when creating the Promise and the parameter resolve is the method of the Deferred object that runs all callbacks on success. 3. method, then () does the same as done () - i.e. callbacks are hung and you use it in case you suddenly need to hang error handlers ... - Serafim

Alternatively, make a wrapper over setTimeout returns a Promise , like this:

 function delay(timeout){ return new Promise(function(r){ setTimeout(r,timeout); }); } 

Now the code from the question might look like this:

 function delay(timeout) { return new Promise(function(r) { setTimeout(r, timeout); }); } function f1() { console.log(1); } function f2() { console.log(2); } function f3() { console.log(3); } delay(3000).then(f1) .then(function(){return delay(2000);}) .then(f2) .then(function(){ return delay(1000);}) .then(f3) .then(function() { console.log('all finish'); }); 

Or even

 function delay(timeout) { return new Promise(function(r) { setTimeout(r, timeout); }); } function f1() { console.log(1); return 2000 } function f2() { console.log(2); return 1000; } function f3() { console.log(3); } delay(3000).then(f1) .then(delay) .then(f2) .then(delay) .then(f3) .then(function() { console.log('all finish'); }); 

If there are many such functions, you can assemble an array from them and use the reduce function to collect one large Promise

 function delay(timeout) { return new Promise(function(r) { setTimeout(r, timeout); }); } function f1() { console.log(1); return 2000 } function f2() { console.log(2); return 1000; } function f3() { console.log(3); } [f1, f2, f3].reduce(function(promise, func) { return promise.then(func).then(delay); }, delay(3000)) .then(function() { console.log('all finish') }); 

  • Something tells me that timers are just an example - Dmitriy Simushev
  • @DmitriySimushev, it’s possible, but in principle, the delay function is simply replaced with any other function that returns promise, and the approach itself remains the same - Grundy
  • Then what is the fundamental difference between your answer and mine? =) - Dmitriy Simushev
  • @DmitriySimushev, because my functions f are not asynchronous themselves :) - Grundy

If we add the same callback report to each, we can hold a queue in the array and call the following function after the previous one is completed:

 function f1(){ setTimeout( function(){ console.log(1); next(); }, 30); } function f2(){ setTimeout( function(){ console.log(2); next(); }, 20); } function f3(){ setTimeout( function(){ console.log(3); next(); }, 10); } var queue = [f1, f2, f3] ,i=0 ,next = function(){ queue[i] && queue[i++]();} ; next(); // 1 2 3 
  • can instead of the counter array as a queue to use? I mean pop or rather shift - Grundy
  • @Grundy as an option - but suddenly there they want to go back a couple of steps back if something went wrong :) - Sergiks
  • This will then be some kind of super queue :-) - Grundy
  • queue [i] && queue [i ++] (); - Serafim
  • @Serafim you do not understand this code snippet? It checks whether there is an element of the queue[i] array containing the pf file, and if so, it performs this function, and then increases i by 1. - Sergiks