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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Burak Karasoy
  • 1,682
  • 2
  • 21
  • 33
  • 1
    I can't tell if you are trying to do a [`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) or if you want your promises to do more of a Waterfall type thing. – zero298 Aug 29 '16 at 18:46
  • 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. – Burak Karasoy Aug 29 '16 at 18:48
  • What is expected result if recursive call which returns first promise is not resolved? – guest271314 Aug 29 '16 at 18:50
  • 1
    What's stopping the first promise from re-running indefinitely? – zero298 Aug 29 '16 at 18:50
  • @guest71314 var i in for will be decreased and first promise will be triggered again – Burak Karasoy Aug 29 '16 at 18:51
  • @BurakKarasoy Expected result is for infinite recursion if first promise is rejected? – guest271314 Aug 29 '16 at 18:52
  • @guest71314 not infinite but code needs to be stubborn(3-4 times max). I'm sending data and it might get lost. – Burak Karasoy Aug 29 '16 at 18:55
  • See http://stackoverflow.com/questions/33718545/promise-try-until-it-succeds/ – guest271314 Aug 29 '16 at 18:56
  • @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. – Burak Karasoy Aug 29 '16 at 19:12
  • @BurakKarasoy Use recursion, `.then()`, named functions, to call function until each step returns resolved promise – guest271314 Aug 29 '16 at 19:13

4 Answers4

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));
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
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);
            }
        }
    }
}
Brandon
  • 38,310
  • 8
  • 82
  • 87
  • 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? – Burak Karasoy Aug 29 '16 at 19:08
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)
});
guest271314
  • 1
  • 15
  • 104
  • 177
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.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • 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 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) You made this function async right? How can making this function async cause that function not to be called immediately? – Burak Karasoy Aug 30 '16 at 17:38
  • @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` function – Redu Aug 30 '16 at 17:49
  • 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 async – Burak Karasoy Aug 30 '16 at 18:12
  • 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.(https://ponyfoo.com/articles/understanding-javascript-async-await) – Burak Karasoy Aug 30 '16 at 18:25
  • @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. – Redu Aug 30 '16 at 18:28
  • thanks. kediCesur would be less descriptive but less confusing as well :) – Burak Karasoy Aug 30 '16 at 18:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/122227/discussion-between-redu-and-burak-karasoy). – Redu Aug 30 '16 at 18:59