How To Synchronise Promise Objects?
Asked Answered
M

4

5

I have promise objects which need to work synchronize. For example second promise shouldn't work before first one is done. If first one rejects first one has to be executed again.

I have implemented some examples.This one works well. call getVal, wait 2000ms, return, i++, again call getVal .....

 getVal() {
       return new Promise(function(resolve, reject) {
      setTimeout(function(){ resolve(19) }, 2000);
         });

     }

async promiseController(){

    for(var i =0;i<5;i++)
      {
        var _val = await this.getVal()
        console.log(_val+'prom');
      }
    }

But I need to control an array of promise objects. What I want to do is that I have a data and I divided it 5 pieces. After first part is processed(for example:sent to server) well I want to process second part otherwise I have to process first part again.

This is the prototype implementation I made

  getVal() {
   return new Promise(function(resolve, reject) {
  setTimeout(function(){ resolve(19) }, 2000);
     });

 }

async promiseController(){
  var proms=[]
  for(var i =0;i<5;i++)
    {
      proms.push(this.getVal())
    }

for(var i =0;i<5;i++)
  {
    var _val = await proms[i]
    console.log(_val+'prom');
  }
}

Promises objects in this code works sequentially. How can i fix the code below so that it works synchronous as first example.

Mathematician answered 29/8, 2016 at 18:41 Comment(10)
I can't tell if you are trying to do a Promise.all() or if you want your promises to do more of a Waterfall type thing.Stakhanovism
promise.all() does not work for me. It rejects the line after one promise down. In my situation I have to make them all work otherwise it is useless.Mathematician
What is expected result if recursive call which returns first promise is not resolved?Lastminute
What's stopping the first promise from re-running indefinitely?Stakhanovism
@guest71314 var i in for will be decreased and first promise will be triggered againMathematician
@BurakKarasoy Expected result is for infinite recursion if first promise is rejected?Lastminute
@guest71314 not infinite but code needs to be stubborn(3-4 times max). I'm sending data and it might get lost.Mathematician
See #33719045Lastminute
@guest713114 I checked the answer but it seems complicated to me, I don't know how to optimize that to work with array of promises.Mathematician
@BurakKarasoy Use recursion, .then(), named functions, to call function until each step returns resolved promiseLastminute
M
3
async promiseController(){
  for(const value of array) {
    console.log((await this.getVal(value))+'prom');
  }
}

No need to overcomplicate things. Just call await inside the loop and it'll wait for what you want.

As the other answer rightfully said - a promise represents a value and not an operation. For operations regular functions are used.

If you want to ignore failures you can .catch(() => {}) on the promise. If you want to retry until failures - you can refactor retry to a function and use that:

const retry = fn => (...args) => fn(...args).catch(retry(fn));
Martainn answered 29/8, 2016 at 19:12 Comment(7)
thanks but as I mentioned in the question i have promise array . Calling getVal does not work for meMathematician
That makes no difference, instead of iterating from 0 to 4 iterate the values. I've updated the example to show that instead of 0..4Martainn
you mean (await this.getVal(value)) or (await value)Mathematician
await this.getVal(value) - keep an array of values and process them in the loop. Once you have promises you've already lost the chance to synchronize them. You synchronize functions.Martainn
'Once you have promises you've already lost the chance to synchronize them' this is the key.thanks but ı don't understand how to use this line const retry = fn => (...args) => fn(...args).catch(retry(fn));Mathematician
You don't need to use it - it's just a generic function for retrying failed promise results.Martainn
thanks for help. If you have a favorite paper, site .. about ecmascript, promises, yields... suggest me i'll be happy.Mathematician
B
1

If your goal is to not "execute the subsequent promises" until the first promise resolves, then you need to keep in mind that promises represent asynchronous activity already in flight. Once the promise exists, it is too late.

You need to instead not call the subsequent promise factory methods until the first promise finishes. Your first example does this by not calling getVal() until the previous promise completes.

So you end up with something like:

delay(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

async promiseController() {
    const factories = [];
    for (let i = 0; i < 5; ++i) {
        factories.push(() => this.getVal());
    }

    for (const f of factories) {
        // keep running this factory until it succeeds
        let success = false;
        while (!success) {
            try {
                const promise = f();
                const result = await f;
                success = true;
                console.log(`result = ${result}`);
            }
            catch (err) {
                console.log("promise failed.  retrying");
                await delay(100);
            }
        }
    }
}
Bucko answered 29/8, 2016 at 18:58 Comment(1)
thanks. You're saying 'Your first example does this by not calling getVal() until the previous promise completes.' What is wrong with 2nd example. What is the difference between await this.getVal() and await proms[i]. Both have a promise ref don't they?Mathematician
L
1

You can use recursion, named function, .then()

var arr = [Promise.resolve("a")
           , Promise.resolve("b")
           , Promise.resolve("c")];
var i = 0;
var res = [];

function foo() {
  // conditional resolved or rejected promise
  var n = String(new Date().getTime()).slice(-1);
  // if `n` < 5 reject `n` , else resolve `n`
  var curr = n < 5;
  return curr ? arr[i] : Promise.reject(["rejected", n])
}

var p = (function repeat() {
  var promise = foo();
  return promise
    .then(function(data) {
      console.log(data);
      res.push(data);
      ++i;
      if (i < arr.length) return repeat()
      // return `res` array when all promises complete
      else return res
    })
    .catch(function(err) {
      console.log(err);
      if (err[0] === "rejected") return repeat()
    })
}());

p.then(function(complete) {
  console.log("complete:", complete)
});
Lastminute answered 29/8, 2016 at 19:35 Comment(0)
S
1

Well OK. I believe for the sake of proper functional programming purposes the async and await thingy should be avoided. I believe promises are very sufficient. Yet if you would like to continue coding in C++ ish imperative style then async and await is for you.

I have promise objects which need to work synchronize. For example second promise shouldn't work before first one is done. If first one rejects first one has to be executed again.

Let me give a little brief on the code below. We have the async() function which takes a data and a callback (error first type). As for demo purposes it will try to invoke the callback with data within 2000ms however there is a timeout at 1000ms. So 50-50 it will either invoke the callback with data or error.

So we actually need it to return us a promise so I promisify it with the help of promisify() and it takes async() function and returns me the asyncPro() function. Which is in fact same as async() but returns a promise instead. So we are expected to use our callback at the then stage.

Then comes tryNTimes(data,asyncFun,n = 5) function which takes data, a promisified async function and an integer designating the number of times to try before rejecting it. It's default try count is 5 but you can set it to any value by passing the third argument.

As for the last part we have the flowControl() which chains up our promises perfectly by the help of Array.prototype.reduce().

So now we have all our promises chained one after the other and none will fail before trying 5 times.

function promisify(fun){
  return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}

function async(data, callback){
  var dur = Math.floor(Math.random()*2000);
  setTimeout(_ => callback(false,data),dur);           // may resolve before timeout
  setTimeout(_ => callback("error at " + data),1000);  // timeout at 1 sec
}

function tryNTimes(data,asyncFun,n = 5){
  return new Promise((resolve,reject) => { n === 0 && reject("try out fail at 5 tries: " + data);
                                           asyncFun(data).then(v => resolve("resolved at countdown " + n + ": " + v))
                                                         .catch(e => resolve(tryNTimes(data,asyncFun,--n)));
                                         });
}

function flowControl(d,f,tc){
  return d.reduce((prom,chunk) => prom.then(v => { console.log(v);
                                                   return tryNTimes(chunk,f,tc);
                                                 }),Promise.resolve("initial dummy promise"));
}

var data = ["chunk_1", "chunk_2", "chunk_3", "chunk_4", "chunk_5"],
asyncPro = promisify(async);                           // now our async function returns a promise

flowControl(data,asyncPro).then(v => console.log(v))
                          .catch(e => console.log(e));

If you would like to see the "5 times try" errors more frequently please lower the timeout value in async() function.

Shu answered 30/8, 2016 at 0:35 Comment(7)
thanks for this detailed and explanatory beatiful example. I didn't understand one step. Promise has a executer function and this function is executed immediately it says (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) You made this function async right? How can making this function async cause that function not to be called immediately?Mathematician
@Burak Karasoy Not the async. Yes when you generate a promise object you have it run an executer function and in our example the executer is in the promisfy function.. (resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)) It will synchronously execute and return you a promise object which will wait for our async function resolution or rejection. If you look asyncPro = promisify(async); instruction makes our async function the fun argument in the promisfy and we are providing our executer's resolve and reject callbacks to the async functionShu
promisify() doesn't return a promise it returns a function which returns a promise this is why the promise is not triggered immediately but after the method is called right? and does the name of the method have to be async in this situation? or Did you call it async just because it has setTimeout(..) can't you name it _asyncFunc instead of asyncMathematician
ok i tried it and got the answer. Since async is a keyword in ecmascript it confused me.I thought it was similar to async-await.(ponyfoo.com/articles/understanding-javascript-async-await)Mathematician
@Burak Karasoy yes true.. promisfy makes an ordinary callback style asynchronous function to return a promise object. It returns a promisified version of our async function. Yes our executer function is embedded in asyncPro. I just called it async just because i couldn't find any better name that moment. It's totally by convention nothing special. You can name it kediCesur if you would like to. :) Sorry for confusing you with the name.Shu
thanks. kediCesur would be less descriptive but less confusing as well :)Mathematician
Let us continue this discussion in chat.Shu

© 2022 - 2024 — McMap. All rights reserved.