5

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?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
  • 4
    This may help: http://davidwalsh.name/async-generators. async/await can be implemented as syntactic sugar over generators + promises. – Felix Kling Dec 01 '14 at 20:48
  • 1
    Have you simply looked at Koa, Co, and Bluebird? They're well documented. – Bergi Dec 01 '14 at 20:49
  • possible duplicate of [Understanding code flow with yield/generators](http://stackoverflow.com/q/23551418/1048572) – Bergi Dec 01 '14 at 20:50
  • @FelixKling thanks for the link- very helpful. If you want to post that as an answer I'd accept it. Seems the answer to my question is _yes_. – Jeremy Danyow Dec 01 '14 at 21:36
  • @Bergi I had looked at Koa, but wasn't really looking for a library/web-framework. Was more interested in how to use generators to make async/promise stlye code more readable. – Jeremy Danyow Dec 01 '14 at 21:37
  • 1
    @JeremyDanyow: Yeah, it's a bit hidden in Koa. Rather have a look at https://github.com/tj/co#examples and https://github.com/petkaantonov/bluebird/blob/master/API.md#generators – Bergi Dec 01 '14 at 22:01

3 Answers3

5

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);
});
framp
  • 813
  • 11
  • 22
  • 3
    Part of the purpose of async/await is to give you the freedom to write syntactically clean code without having to resort to "options" like this. Yes, this does basically the same thing, but it is still not actually the same thing, because the **syntax** is just as important the **semantics** with async/await. – jrista Aug 05 '15 at 05:49
  • 1
    Of course, it's not the same thing. The OP was asking how to use generators to achieve async/await. – framp Aug 06 '15 at 05:16
  • Indeed. Just pointing out an important distinction. One note...the OP was looking for a solution that does not use callbacks...the async() function still requires a callback. Any way to avoid that? – jrista Aug 06 '15 at 15:48
  • I don't think so. Anyway you're not using callbacks to return data, you're just wrapping your code with a generator. The code inside doesn't need callbacks. For example you can try using `yield get` several time – framp Aug 08 '15 at 08:15
  • I think that may be due to the use of `console.log(data)`. If you actually needed to do something with data, then function *(resume) is effectively a callback for async(), because it's a per-use-case thing. Similarly, if you wanted to make the async bit more generic, then you would need a callback somewhere or you would need to use a promise. Anyway, it's a close enough solution, but I just wanted to point out it doesn't entirely accomplish the simplicity of what async/await bring to the table. ;) – jrista Aug 09 '15 at 01:33
  • It'd be nice if you could write an async/await example exampe equivalent to one of the generator examples as reference for those stumbling here learning about both. – trusktr Nov 06 '15 at 06:04
  • Probably slightly outside the scope of the answer but it's `async function() { var user = await getUser(user_id); /* Do something with your user */ }` – framp Nov 09 '15 at 15:01
1

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.

basarat
  • 261,912
  • 58
  • 460
  • 511
  • There are a couple of libraries for async programming using generators, e.g. https://github.com/bjouhier/galaxy. But IMO the best option at the moment is [streamline.js](https://github.com/Sage/streamlinejs), because it gives you multiple options for the transformed source code. If you care about browser compatibility at all, you'd want to go with the regular ECMAScript 3 output, but it does also have the option to have the output be ES6 generators (using the "galaxy" library). – Matt Browne Feb 27 '15 at 00:00
1

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);
  });
}
orad
  • 15,272
  • 23
  • 77
  • 113
  • How would you use async/await with a 6-to-5 transpiler when they're not part of ES6? – Bergi Sep 19 '15 at 21:20
  • It uses Promises behind the scenes, so it runs in ES5. async/await are proposals of course but some transpilers like TypeScript give you options for experimental support. – orad Sep 19 '15 at 21:30
  • 1
    OK, fixed it. Should use a ES7 (experimental) to ES5 traspiler. – orad Sep 19 '15 at 21:40
  • 1
    It'd be nice if you could write an async/await example exampe equivalent to one of the generator examples as reference for those stumbling here learning about both. – trusktr Nov 06 '15 at 06:04