<!DOCTYPE html>
<html>

  <head>
    <title>Async Fun: Six Demos</title>
    <script data-require="jquery@2.1.1" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <link rel="stylesheet" href="style.css" />
  </head>
 
  <body>
    <h1>ASync Fun!</h1>
    <section class="container">
      <h2>Status:</h2>
      <ul id="status"></ul>
    </section>
    <section class="container">
      <h2>Responses:</h2>
      <h3>Profile:</h3>
      <pre id="profile"></pre>
      <h3>Tweets:</h3>
      <pre id="tweets"></pre>
      <h3>Mentioned Friend:</h3>
      <pre id="friend"></pre>
    </section>
    <script src="scripts.js"></script>
  </body>

</html>
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');

body {
  font-family: 'Source Sans Pro', sans-serif;
}

h1 {
  text-align: center;
}

pre {
  white-space: pre-line;
}

.container {
  border: 3px solid #ccc;
  padding: 20px;
  margin: 10px 0;
}

.error {
  color: red;
}

.success {
  color: green;
}
# Async Fun

Describes 5 examples within `scripts.js` for processing data from an API, in order of increasing _awesomeness_, including:
1. Callbacks
2. Cleaner, named function-based callbacks
3. Promises
4. Fetch (a promise-based API that is part of ES6)
5. Generator Functions
 
Made for a Girl Develop It ES6 course.
{
  "id": 10,
  "username": "GDIBoston"
}
[
  {
   "id": 1,
   "tweet": "Excited about tonight's @GDIBoston JavaScript class!",
   "usersMentioned": [
     {
      "id": 10,
      "username": "GDIBoston"
     }
    ]
  },
  {
   "id": 2,
   "tweet": "Today's the second JavaScript class, yay!"
  },
  {
   "id": 3,
   "tweet": "So ready to code & make some websites!"
  },
  {
   "id": 4,
   "tweet": "Just graduated from my @GDIBoston JavaScript class :)",
   "usersMentioned": [
     {
      "id": 10,
      "username": "GDIBoston"
     }
    ]
  }
]
{
  "id": 12302151,
  "username": "coderlady5000",
  "authLevel": "user",
  "prefs": {
    "homepageTweetsToShow": 2,
    "showSidebar": false,
    "fontSize": 12
  }
}
/*
  ------------------------------
  SHARED VARIABLES AND FUNCTIONS
  ------------------------------
*/
const statusContainer = document.getElementById('status');
const profileContainer = document.getElementById('profile'); 
const tweetsContainer = document.getElementById('tweets'); 
const mentionedFriendContainer = document.getElementById('friend');

// Promise-based request() function
const request = url => {
     return new Promise((resolve, reject) => {
          var xhr = new XMLHttpRequest();
          xhr.open('GET', url);
          xhr.onload = function() {
               if (this.status >= 200 && this.status < 300) { 
                    resolve(JSON.parse(xhr.response));
               } else {
                    reject({
                         status: this.status,
                         statusText: xhr.statusText
                    });
               }
          };
          xhr.onerror = function() {
               reject({
                    status: this.status,
                    statusText: xhr.statusText
               });
          };
          xhr.send();
     });
}

function handleError(err) {
     if (err.status) {
          statusContainer.innerHTML += `<li>Error ${err.status}: ${err.statusText}</li>`;
     } else {
          statusContainer.innerHTML += `<li>Error: ${err.toString()}</li>`;
     }
     statusContainer.classList.add('error');
}



//------------------------------------------------------------------------------



/*
  --------------------
  VERSION 1: CALLBACKS
  --------------------
*/

// Get profile, then tweets, then mentioned friend

// STEP 1: Get profile
$.ajax({
     type: 'GET',
     url: 'json_profile.json', // Getting a JSON file acts just like hitting an API
     success: profile => {

          $(statusContainer).append('<li>Fetched profile</li>');
          $(profileContainer).html(JSON.stringify(profile));

          // STEP 2: Get tweets
          $.ajax({
               type: 'GET',
               url: `json_tweets.json?id=${profile.id}`,
               success: tweets => {
                    $(statusContainer).append('<li>Fetched tweets</li>');
                    $(tweetsContainer).html(JSON.stringify(tweets));

                    // STEP 3: Get mentioned friend
                    $.ajax({
                         type: 'GET',
                         url: `json_friend.json?id=${tweets[0].usersMentioned[0].id}`,
                         success: friend => {
                              $(statusContainer).append('<li>Fetched mentioned friend</li>');
                              $(mentionedFriendContainer).html(JSON.stringify(friend));
                         },
                         error: handleError
                    });
               },
               error: handleError
          });

     },
     error: handleError
});



//------------------------------------------------------------------------------



/*
  -----------------------------------------------
  VERSION 2: CLEANER FUNCTION-REFENCING CALLBACKS
  -----------------------------------------------
*/

// // STEP 1: Get profile
// $.ajax({
//      type: 'GET',
//      url: 'json_profile.json', // Getting a JSON file acts just like hitting an API
//      success: getTweets, // STEP 2: Get tweets
//      error: handleError
// });

// function getTweets(profile) {

//      $(statusContainer).append('<li>Fetched profile</li>');
//      $(profileContainer).html(JSON.stringify(profile));
     
//      // Get tweets, passing our profile id
//      $.ajax({
//           type: 'GET',
//           url: `json_tweets.json?id=${profile.id}`,
//           success: getMentionedUser, // STEP 3: Get mentioned user
//           error: handleError
//      });
// }

// function getMentionedUser(tweets) {
//      $(statusContainer).append('<li>Fetched tweets</li>');
//      $(tweetsContainer).html(JSON.stringify(tweets));

//      // Get friend mentioned in first tweet
//      $.ajax({
//           type: 'GET',
//           url: `json_friend.json?id=${tweets[0].usersMentioned[0].id}`,
//           success: friend => {
//                $(statusContainer).append('<li>Fetched mentioned friend</li>');
//                $(mentionedFriendContainer).html(JSON.stringify(friend));
//           },
//           error: handleError
//      });
// }



//------------------------------------------------------------------------------



/*
  -------------------------------
  VERSION 3: PROMISES
  -------------------------------
*/

// // STEP 1: Get profile
// request('json_profile.json')
//      .then(profile => {
//           statusContainer.innerHTML += '<li>Fetched profile</li>';
//           profileContainer.innerHTML = JSON.stringify(profile);

//           // STEP 2: Get tweets
//           return request(`json_tweets.json?id=${profile.id}`);
//      })
//      .then(tweets => {
//           statusContainer.innerHTML += '<li>Fetched tweets</li>';
//           tweetsContainer.innerHTML = JSON.stringify(tweets);

//           // STEP 3: Get mentioned friend
//           return request(`json_friend.json?id=${tweets[0].usersMentioned[0].id}`);
//      })
//      .then(friend => {
//           statusContainer.innerHTML += '<li>Fetched mentioned friend</li>';
//           mentionedFriendContainer.innerHTML = JSON.stringify(friend);
//      })
//      .catch(err => handleError(err));



//------------------------------------------------------------------------------



/*
  ------------------------------------------------------------
  VERSION 4: FETCH 
  Fetch is a promise-based API built into ES6 that we can use. 
  Much easier than writing our own request() function!
  ------------------------------------------------------------
*/

// // STEP 1: Get profile
// fetch('json_profile.json') // Getting a JSON file acts just like hitting an API

//      // Return response JSON for processing in next then()
//      .then(response => response.json())
//      .then(profile => {
//           statusContainer.innerHTML += '<li>Fetched profile</li>';
//           profileContainer.innerHTML = JSON.stringify(profile);

//           // Get tweets, passing our profile id
//           return fetch(`json_tweets.json?id=${profile.id}`);
//      })

//      // STEP 2: Get tweets
//      // Return response JSON for processing in next then()
//      .then(tweetsResponse => tweetsResponse.json())
//      .then(tweets => {
//           statusContainer.innerHTML += '<li>Fetched tweets</li>';
//           tweetsContainer.innerHTML += JSON.stringify(tweets);

//           // Get friend mentioned in first tweet
//           return fetch(`json_friend.json?id=${tweets[0].usersMentioned[0].id}`);
//      })

//      // STEP 3: Get mentioned friend
//      // Return response JSON for processing in next then()
//      .then(friendResponse => friendResponse.json())
//      .then(friend => {
//           statusContainer.innerHTML += '<li>Fetched mentioned friend</li>';
//           mentionedFriendContainer.innerHTML += JSON.stringify(friend);
//      })
//      .catch(err => handleError(err));
  
  
  
//------------------------------------------------------------------------------
  


/*
  ------------------------------
  VERSION 5: GENERATOR FUNCTIONS
  ------------------------------
*/

// function* createTweetFetcher() {
//      // STEP 1: Get profile
//      const profileID = yield request('json_profile.json');

//      // STEP 2: Get tweets
//      const friendID = yield request(`json_tweets.json?id=${profileID}`);
     
//      // STEP 3: Get mentioned friend
//      yield request(`json_friend.json?id=${friendID}`);
// }

// const tweetFetcher = createTweetFetcher();
// tweetFetcher.next().value
//      .then(profile => {
//           statusContainer.innerHTML += '<li>Fetched profile</li>';
//           profileContainer.innerHTML = JSON.stringify(profile);
//           return tweetFetcher.next(profile.id).value;
//      })
//      .then(tweets => {
//           statusContainer.innerHTML += '<li>Fetched tweets</li>';
//           tweetsContainer.innerHTML += JSON.stringify(tweets);
//           const { id: friendID } = tweets[0].usersMentioned[0];
//           return tweetFetcher.next(friendID).value;
//      })
//      .then(friend => {
//           statusContainer.innerHTML += '<li>Fetched mentioned friend</li>';
//           mentionedFriendContainer.innerHTML = JSON.stringify(friend);
//           return tweetFetcher.next(); // final next() switches generator to done:true
//      })
//      .catch(err => handleError(err));
 
 
 
//------------------------------------------------------------------------------



/*
  ---------------------------------------------------------
  VERSION 6: GENERATOR FUNCTIONS WITH RUNNER FUNCTION
  - Source: https://davidwalsh.name/async-generators
  ---------------------------------------------------------
*/

// function* createTweetFetcher() {
//      // STEP 1: Get profile
//      const profile = yield request('json_profile.json');
//      statusContainer.innerHTML += '<li>Fetched profile</li>';
//      profileContainer.innerHTML += JSON.stringify(profile);
     
//      // STEP 2: Get tweets
//      const tweets = yield request(`json_tweets.json?id=${profile.id}`);
//      statusContainer.innerHTML += '<li>Fetched tweets</li>';
//      tweetsContainer.innerHTML += JSON.stringify(tweets);
     
//      // STEP 3: Get mentioned friend
//      const friend = yield request(`json_friend.json?id=${tweets[0].usersMentioned[0].id}`);
//      statusContainer.innerHTML += '<li>Fetched mentioned friend</li>';
//      mentionedFriendContainer.innerHTML = JSON.stringify(friend);
// }

// // A runner functions run a generator function to completion
// function runGenerator(generator) {
//      const iterator = generator();

//      // Asynchronously iterate over generator
//      (function iterate(val){
//           const yielded = iterator.next(val);

//           if (!yielded.done) {
//                // {done: false} -> since the yielded value is not 'done', continue
          
//                if (typeof yielded.value.then === 'function') {
//                     // the yielded value is a promise
//                     yielded.value
//                          .then(iterate)
//                          .catch(err => handleError(err));
//                } else {
//                     // Immediate value: just send right back in and 
//                     // avoid synchronous recursion
//                     setTimeout(() => iterate(yielded.value), 0);
//                } 
//           }
//      })();
// }

// // Run the createTweetFetcher generator function to completion
// runGenerator(createTweetFetcher);