Derek 朕會功夫
Derek 朕會功夫

Reputation: 94369

Converting await in infinite for loop into raw promise.then

As far as I know, async/await is just syntactic sugar over promise.then. Consider this code snippet:

function sleep(n){
    return new Promise(res => setTimeout(res, n));
}

function* range(n){
    var i = 0;
    while(i < n)    yield i++;
}
async function doStuff(){
    for(let n of range(10)){
        console.log(n);          // print the number
        await sleep(1000);       // wait for 1 second
    }
}

async/await makes the code very linear, efficient and easy to understand. One thing to keep in mind is that range does not have to have an actual end for this to work.

The problem now is how this can be rewritten using pre-ES7 era's promise.then. Here's a possible implementation of the same loop:

function doStuff(){
    return Array.from(range(10)).reduce((acc, ele) => {
        return acc
            .then(() => console.log(ele))
            .then(() => sleep(1000))
    }, Promise.resolve());
}

Ignoring the fact that the code isn't quite elegant, the use of Array.from(range(10))

  1. creates an extra array that isn't needed, and
  2. assumes range(10) will end some point in the future.

Doesn't look like a good conversion.

We can also completely reinvent the wheel by using yield as await, but that would make the syntax non ES5-compliant. The goal here is to:

  1. Rewrite using ES5-compliant syntax
  2. Use the promise-returning sleep function
  3. Dynamically chain the sleep promise while allowing the iterator to not have an end
  4. doStuff can be chained:

    doStuff().finally(cleanUp);  // clean up if something failed
    
  5. (Optional) Code should not be overly complex

Any idea?

Upvotes: 4

Views: 246

Answers (2)

slebetman
slebetman

Reputation: 114004

I've always said that if you need an asynchronous design pattern first look at the async library. In this case, since you're using promises, take a look at the promisified async-q library. The translation is straight forward:

var n = 0;
async.whilst(() => n < 10, () => {
    n++;
    console.log(n);
    return sleep(1000);
})

Upvotes: 1

HMR
HMR

Reputation: 39320

I think the following may do the trick, your example doesn't show what to do with resolve value and how it relates to the iterator values so I made a change to how sleep is called.

Some promise polyfils may run out of stack space with high promise chains so you should check your polyfil (if its implementation returns and continues with a setTimeout the stack should clear but some polyfils may not implement it this way).

    function sleep(n){
      return new Promise(res => setTimeout(_=>res(n/100), n));
    }

    function* range(n){
      var i = 0;
      while(i < n)    yield i++;
    }

    function doStuff(){
      const processValue = 
        resolve => {
          console.log("resolved with:",resolve);
          // if(resolve===3){throw "nope";}
          return sleep(resolve*100);
        },
      rec = (p,iter) => {
        const result = iter.next();
        if (result.done){
          return p;
        }
        p = p.then(_=>processValue(result.value))
        return p.then(
          resolve=>{
            return rec(p,iter)
          }
        );
      },
      iter = range(10),
      firstResult = iter.next();
      if(firstResult.done){
        return processValue(firstResult.value);
      }
      return rec(processValue(firstResult.value),iter);
    }

    doStuff()
    .then(
      x=>console.log("done:",x)
      ,reject=>console.warn("fail:",reject)
    );

Upvotes: 1

Related Questions