Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 generators- are they really an alternative to async/await?

One of the posts in the comments section of this typescript blog post says:

If I have to wait until 2.0 for ES6 generator support, I'm just gonna stick with Traceur. Generators are a big deal, they give you async/await support today using libraries such as Koa, Co, Bluebird, etc.

Async/await keywords would allow applications to retain a logical structure that resembles synchronous code. How would one use a generator to accomplish something similar? For example, how would you use a generator in conjunction with an ajax call to produce synchronous style code that avoids using callbacks?

like image 545
Jeremy Danyow Avatar asked Dec 01 '14 20:12

Jeremy Danyow


People also ask

Is async await based on generators?

Async/await makes it easier to implement a particular use case of Generators. The return value of the generator is always {value: X, done: Boolean} whereas for async functions, it will always be a promise that will either resolve to the value X or throw an error.

Does ES6 have async await?

Async and Await both are considered as special keywords which are provided by ES6 in order to perform some asynchronous data operations.

What are ES6 generators?

Generator (or Generator function) is the new concept introduced in ES6. It provides you a new way of working with iterators and functions. ES6 generator is a different kind of function that may be paused in the middle either one or many times and can be resumed later.


3 Answers

You just have to abstract that with an helper function.

Assuming jQuery:

function ajax(type, url, data){
  $.ajax({
    url: url,
    data: data,
    type: type
  })
  .done(function(data) {
    iterator.next(data);
  })
  .fail(function() {
    iterator.throw();
  });
}
var get = ajax.bind(null, 'GET');
var post = ajax.bind(null, 'POST');
var put = ajax.bind(null, 'PUT');
var patch = ajax.bind(null, 'PATCH');
var del = ajax.bind(null, 'DELETE');

function *asyncGet() {
  var URL = 'https://api.stackexchange.com/2.2/answers?order=desc&sort=activity&site=stackoverflow'
  var data = yield get(URL);
  console.log(data);
}

var iterator = asyncGet();
iterator.next();

Another example using setTimeout:

function delayReturn(time, val){
  setTimeout(function(){
    iterator.next(val);
  }, time);
}
var delayReturn1s = delayReturn.bind(null, 1000);

function *main() {
  console.log(yield delayReturn1s('Lolcat'));
}

var iterator = main();
iterator.next()

Of course you can abstract the iterator passing with something like this:

var async = function(generator){
  var resume = function(err, data){
    if (err) iterator.throw();
    iterator.next(data);
  }
  var iterator = generator(resume);
  iterator.next();
}

Then you can simply:

function ajax(type, url, data, cb){
  $.ajax({
    url: url,
    data: data,
    type: type
  })
  .done(function(data) {
    cb(null, data)
  })
  .fail(function() {
    cb(arguments);
  });
}
var get = ajax.bind(null, 'GET');
var post = ajax.bind(null, 'POST');
var put = ajax.bind(null, 'PUT');
var patch = ajax.bind(null, 'PATCH');
var del = ajax.bind(null, 'DELETE');

async(function *(resume) {
  var URL = 'https://api.stackexchange.com/2.2/answers?order=desc&sort=activity&site=stackoverflow'
  var data = yield get(URL, null, resume);
  console.log(data);
});
like image 101
framp Avatar answered Nov 14 '22 22:11

framp


For example, how would you use a generator in conjunction with an ajax call to produce synchronous style code that avoids using callbacks?

From Beginning Node.js :


As a thought experiment imagine the following, a way to tell the JavaScript runtime to pause the executing of code on the await keyword used on a promise and resume only once (and if) the promise returned from the function is settled.

// Not actual code. A thought experiment
async function foo() {
    try {
        var val = await getMeAPromise();
        console.log(val);
    }
    catch(err){
        console.log('Error: ',err.message);
    }
}

When the promise settles execution continues, if it was fulfilled then await will return the value, if it's rejected an error will be thrown synchronously which we can catch. This suddenly (and magically) makes asynchronous programming as easy as synchronous programming. Three things are needed:

  • Ability to pause function execution.
  • Ability to return a value inside the function.
  • Ability to throw an exception inside the function.

The good news is this magic is very real, and possible to try today. The syntax will be slightly different, because the technology we will be using wasn't designed only for this. It is possible because of JavaScript generators, a technology coming with ECMAScript 6 that you can use today.


Generators allow you to pause a function execution (using the yield keyword) return a value inside (using the .next(val) function) and throw and exception inside (using the .throw(err) function). These APIs are explained in the book and you can also view them on generator documentation. Still you should get the point / power even without understanding the exact API as you now know the correlation.

like image 35
basarat Avatar answered Nov 14 '22 22:11

basarat


From Thomas Hunter's blog post The long road to Async/Await in JavaScript

Stage 3: Generators/Yields (ES6) shows an example of using ES6 generators to do async JavaScript. However, this is for demonstration only and most likely you don't want to use this technique. Use async/await instead with an ES7 (experimental) to ES5 traspiler such as Babel or TypeScript.

var generator = publishLevel(12, {data: true});

generator.next().value.then(function(user) {
   return generator.next(user).value.then(function(can_create) {
     return generator.next(can_create).value.then(function(level_result) {
       console.log(level_result);
     });
   });
 });

function * publishLevel(user_id, level_data) {
  var user = yield getUser(user_id);
  var can_create = yield canCreate(user);

  if (!can_create) {
    return null;
  }

  var level = yield saveLevel(user, level_data);

  return level;
}

function getUser(user_id) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve({
        id: user_id,
        nickname: 'tlhunter'
      });
    }, 100);
  });
}

function canCreate(user) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(user.id === 12);
    }, 100);
  });
}

function saveLevel(user, data) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve({
        id: 100,
        owner: user.nickname,
        data: data
      });
    }, 100);
  });
}
like image 36
orad Avatar answered Nov 14 '22 22:11

orad