JavaScript
Article

Asynchronous APIs Using the Fetch API and ES6 Generators

By Ravi

ECMAScript 6 (a.k.a. ECMAScript 2015 or ES6) brings a number of new features to JavaScript which will make the language a good fit for large applications. One of these features is better support for asynchronous programming using promises and generators. Another is the addition of the Fetch API which aims to replace XMLHttpRequest as the foundation of communication with remote resources.

The Fetch API’s methods return ES6 Promise objects, which can be used in conjunction with generators to form the basis of complex asynchronous operations. This could be anything from a chain of asynchronous operations, where each operation depends on the value returned by the previous one, to an asynchronous call that has to be made repeatedly to a server to get the latest update.

In this article we will see how the Fetch API can be used in conjunction with generators to build asynchronous APIs. The Fetch API is currently supported in Chrome, Opera, Firefox and Android browsers. We have a polyfill available from GitHub for unsupported browsers.

As ever, the code for this article can be found on our GitHub repository and there is a demo of the final technique at the bottom of the article.

Generators for Asynchronous Operations

Tip: If you need a refresher on what generators are and how they work, check out: ECMAScript 2015: Generators and Iterators

So how can we use generators to perform async operations? Well, if we analyze the way generators work we will find the answer.

A generator function implementing an iterator has the following structure:

function *myIterator(){
  while(condition){
    //calculate next value to return
    yield value;
  }
}

The yield keyword is responsible for returning a result and halting execution of the iterator function until it is next invoked. It also keeps the state of the function instead of rerunning everything when next you call it, effectively remembering the last place it left off.

We can re-imagine the above function without while loop as follows:

function *myIterator(){
  //calculate value 1
  yield value1;

  //calculate value 2
  yield value2;
  ...

  //calculate value n
  yield valuen;
}

The behavior of the function will be identical in both of the above cases. The only reason for using the yield keyword is to pause the execution of the function until the next iteration (which in itself seems kind of asynchronous). And as the yield statement can return any value, we can also return promises and make the function run multiple asynchronous calls.

Using Generators with the Fetch API

Tip: For a refresher on the Fetch API, check out: Introduction to the Fetch API

As mentioned earlier the Fetch API is intended to replace XMLHttpRequest. This new API provides control over every part of an HTTP request and returns a promise that either resolves or rejects based on the response from the server.

Long Polling

One of the use cases where the Fetch API and generators can be used together is long polling. Long polling is a technique in which a client keeps sending requests to a server until it gets a response. Generators can be used in such a case to keep yielding the responses until the response contains data.

To mimic long polling, I included an Express REST API in the sample code that responds with weather information of a city after five attempts. The following is the REST API:

var polls=0;

app.get('/api/currentWeather', function(request, response){
  console.log(polls, polls<5);
  if(polls < 5){
    console.log("sending...empty");
    polls++;
    response.send({});
  }
  else{
    console.log("sending...object");
    response.send({
      temperature: 25,
      sky: "Partly cloudy",
      humid: true
    });
    polls = 0;
  } 
});

Now, let’s write a generator function that calls this API multiple times and returns a promise on every iteration. Being on the client side, we don’t know after how many iterations we will get data from the server. So, this method will have an infinite loop pinging the server on every iteration and returning the promise on every occasion. Following is the implementation of this method:

function *pollForWeatherInfo(){
  while(true){
    yield fetch('/api/currentWeather',{
      method: 'get'
    }).then(function(d){
      var json = d.json();
      return json;
    });
  }
}

We need a function to keep calling this function and checking if the value exists after the promise resolves. It will be a recursive function that invokes the next iteration of the generator and only stops the process when it finds a value returned from the generator. The following snippet shows the implementation of this method and a statement that calls this method:

function runPolling(generator){
  if(!generator){
    generator = pollForWeatherInfo();
  }

  var p = generator.next();
  p.value.then(function(d){
    if(!d.temperature){
      runPolling(generator);
    } else {
      console.log(d);
    }
  });
}

runPolling();

As we see here, the first call to the function runPolling creates the generator object. The next method returns an object with a value property which in our case contains a promise returned by the fetch method. When this promise resolves, it will either contain an empty object (returned if the polls variable is below 5), or an object containing the desired information.

Next, we check for the temperature property of this object (which would indicate success). If it’s not present we pass the generator object back to the next function call (so as not to lose the state of the generator) or we print the value of the object to the console.

To see this in action, grab the code from our repo, install the dependencies, start the server, then navigate to http://localhost:8000. You should see the following results in the shell:

0 true
sending...empty
1 true
sending...empty
2 true
sending...empty
3 true
sending...empty
4 true
sending...empty
5 false
sending...object

And the object itself logged to the browser console.

Multiple Dependent Asynchronous Calls

Quite often, we need to implement multiple dependent asynchronous calls, where each successive asynchronous operation depends on the value returned by the the preceding asynchronous operation. If we have a group of such operations and they have to be called multiple times, we can put them together in a generator function and execute it whenever we need it.

To demonstrate this, I will be using GitHub’s API. This API provides us access to basic information on users, organizations and repos. We will use this API to get the list of contributors to a random repo of an organization and display the fetched data on the screen.

For this we need to make calls to three different endpoints. These are the tasks to be performed:

  • Get details of the organization
  • If the organization exists, get the organization’s repos
  • Get contributors to one of the organization’s repos (selected at random)

Let’s create a wrapper function around Fetch API to avoid repeating the code to create the headers and build the request object.

function wrapperOnFetch(url){
  var headers = new Headers();
  headers.append('Accept', 'application/vnd.github.v3+json');
  var request = new Request(url, {headers: headers});

  return fetch(request).then(function(res){
    return res.json();
  });
}

The following function consumes the above function and yields a promise for each invocation:

function* gitHubDetails(orgName) {
  var baseUrl = "https://api.github.com/orgs/";
  var url = baseUrl + orgName;

  var reposUrl = yield wrapperOnFetch(url);
  var repoFullName = yield wrapperOnFetch(reposUrl);
  yield wrapperOnFetch(`https://api.github.com/repos/${repoFullName}/contributors`);
}

Now, let’s write a piece of logic to call the above function to get the generator and then use the values obtained from the server to populate the UI. As every call to the generator’s next method returns a promise, we will have to chain these promises. The following is the skeleton of the code using the generator returned by the above function:

var generator = gitHubDetails("aspnet");

generator.next().value.then(function (userData) {
  //Update UI

  return generator.next(userData.repos_url).value.then(function (reposData) {
    return reposData;
  });
}).then(function (reposData) {
  //Update UI

  return generator.next(reposData[randomIndex].full_name).value.then(function (selectedRepoCommits) {
    //Update UI
 });
});

To see this in action, as detailed above, grab the code from our repo, install the dependencies, start the server, then navigate to http://localhost:8000. Or just check out the demo below (try rerunning it).

Demo

See the Pen Multiple Dependent Asynchronous Calls With the Fetch API by SitePoint (@SitePoint) on CodePen.

Conclusion

In this article I have demonstrated how the Fetch API can be used in conjunction with generators to build asynchronous APIs. ECMAScript 6 will bring a slew of new features to the language and looking for inventive ways to combine them and harness their power can often bring outstanding results. But what do you think? Is this a technique we can start using in our apps today? I would love to hear your thoughts in the comments.

  • http://www.crocodillon.com/ Dillon de Voor

    In the first example, what is the benefit of a generator over just simply returning the fetch promise (or even not having the pollForWeatherInfo helper function at all)? I’m just trying to find a real world use case for generators where it results in less or easier readable instead of more and harder readable code.

    function pollForWeatherInfo() {
    return fetch('/api/currentWeather',{
    method: 'get'
    }).then(function (d) {
    var json = d.json();
    return json;
    });
    }

    function runPolling(){
    pollForWeatherInfo().then(function (d) {
    if (!d.temperature) {
    runPolling();
    } else {
    console.log(d);
    }
    });
    }

    runPolling();

    • Tatsh

      Most use cases for generators are not exactly within something like promises (although one example I can cite is the new AWS SDK for PHP, version 3). They are more easily seen and understood in something like a very large list of numbers, where having the entire list at once is not necessary and not efficient compared to trying to allocate memory for all the numbers at once. Also useful if you have a case where you break out of the loop before seeing all the values, so you have no wasted space on values not used. This is probably best seen performance-wise with garbage collecting languages.

  • http://careersreport.com virgie_foster2

    tretretet

  • http://careersreport.com virgie_foster2

    Here is a technique how you can earn eighty-five dollars an hour… After being unemployed for half a year , I started working over this website and now I can not be more happy. After 3 months doing this my income is around five-thousand $/month -Check internet website check out my disqus profile for more info

  • http://www.philipjcox.net Philip J Cox

    Nice article, thank you. I love to see people advocating this use of Generators with Fetch/Promises. I believe it is really going to help us right better more readable async code. Kyle Simpson (getify) has also written some great stuff about this subject.

  • Igor Dubinskiy

    Couldn’t the second example be written more simply as:

    var generator = gitHubDetails("aspnet");

    generator.next().value.then(function (userData) {
    //Update UI

    return generator.next(userData.repos_url).value;
    }).then(function (reposData) {
    //Update UI

    return generator.next(reposData[randomIndex].full_name).value.then(function (selectedRepoCommits) {
    //Update UI
    });
    });

    No need to nest promises.

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Yes, as the same object is returned from the then block, we can omit the then block and just return the promise.

  • Khyathi

    i have usd ur sample and tried to implement using traceur.. I am getting the error saying $createPrivateName is not function. Can you help me in solving this.

  • Prasanna Chowdary

    when i am trying this code in safari, along with babel-polyfill , I am getting errors saying “can’t find variable “Headers”… ignorer to resolve this, I added “whatwg-fetch” plugin, and now I am getting errors saying “generator.next().value” is undefined. Can some one please help me in resolving this issue

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.