Skip to content

Instantly share code, notes, and snippets.

@piboistudios
Last active March 14, 2022 16:45
Show Gist options
  • Save piboistudios/f7c38df45f998560f107cb673921da45 to your computer and use it in GitHub Desktop.
Save piboistudios/f7c38df45f998560f107cb673921da45 to your computer and use it in GitHub Desktop.
ES6 Promises

ES6 Promises in a nutshell

Whenever you see someFunction(...).then(...)

(And may be followed with .catch(...) To handle errors)

That means:

  • someFunction returns a Promise
  • then(...) handles the promise once it is fulfilled (or resolved)
  • catch(...) handles the error (if one occurs during the promise) (e.g. when it is rejected)

This happens in the background. (In technical terms, "it runs asynchronously")

So for example, if you had:

// Say time is a number, action is a function
function delay(time, action) {
  // this is how you "roll your own" promise from scratch
  // under the hood, the fetch function from your assignment creates a promise much like this

  return new Promise(function(resolve, reject) { // worth noting you can name resolve and reject anything, but typically with a Promise, these are just called resolve and reject

  // Wait time milliseconds then run the action function
    setTimeout(function() { // the setTimeout function just delays a function by some time in MS; 
                  // all JS implementations have this
      try {
        let result = action();
        resolve(result); // this triggers .then(...) if one is attached
      } catch(error) {
        reject(error); // this triggers .catch(...) if one is attached
          // otherwise if an error happens and there's no .catch(...) you get
               // a warning about an unhandled promise rejection
          // or depending on the JS engine and version, the program may just crash, like a regular error
      }

    }, time);
  }); 
}

You could then do this:

delay(5000, function() {
  console.log('This happened after 5 seconds');
  return "OK";
})
  .then(function(result) {
    console.log(result); // OK
  })
  .catch(function(error) {
    console.error(error); // this doesn't happen in this case as the function above should never fail
  });

Or (handling errors):

delay(5000, function() {
  return "string".push(1); // Strings don't have a push function, arrays do
})
  .then(function(result) {
    console.log("This never happens! The above function is impossible!");
  })
  .catch(function(error) {
    console.error(error); // "string".push is not a function
  });

You may be thinking "Damn this is a lot" That's why fat arrows came in:

delay(5000, () => "string".push(1)) // still an error
.catch(error => console.error(error)); // "string".push is not a function

delay(1000, () => {
  console.log("This happened after 1 second");
  return "OK";
})
  .then(result => console.log(result)); // OK

Note that promises run asynchronously (in other words, they run in the background, the technical term is they "don't block"):

console.log("This prints first");
delay(1000, () => console.log("This prints last"));
console.log("This prints second");

Callbacks, before ES6 Promises, basically

Promises didn't always exist. The alternative to handling "asynchronous" operations in JS was callbacks (as you see in setTimeout, where it takes a function as a parameter, and often you will create Promises using callbacks as in the example above; or you will use another Promise, as long as the function does not block.. otherwise the Promise is pointless, because the code will just stop AKA be "blocked" until it finishes anyways).

With callbacks, the above might look like this:

function delay(time, action, callback) {
  setTimeout(function() {
    try {
      callback(null, action());
    } catch(error) {
      callback(error, null);
    }
  }, time);
}

Then you would do:

delay(1000, () => "string".push(1), (error, result) => {
  if(error) {
    return "Damn I got an error here: " + error;
  } else {
    // do whatever with the result
  }
});

Might not look so bad, however, what if you wanted to run another function after this returns that also returns asynchronously (e.g. not immediately), using our delay function, well...

delay(1000, () => "Whatever", (error, result) => {
  if(error) {
    // won't get an error this time who cares
  } else {
    delay(2500, () => "Damn, this is getting...", (error, result) => {
      if(error) {
        // blargh... 
      } else {
        delay(5000, () => "Really fucking ugly....", (error, result) => {
          if(error) {
            // croikey...
          } else {
            /// and so on....
          }
        })
      }
    })
  }
});
// with the Promise version, it would look like this:
delay(1000, () => "Whatever")
  .then(result => delay(2500, () => "Whoa this isn't..."))
  .then(result => delay(5000, () => "Nearly as fucking bad..."))

Here's a repl with all the code above (pretty much) it will print it out to the console with delays and you'll be able to see how it all works.

You will notice it doesn't run in any coherent order. That's because it uses Promises (and callbacks). You could chain all of the promises together and it would run in a coherent order if you wanted (as in the last code example above example).

But basically, this is what you saw in your assignment:

fetch("https://handlers.education.launchcode.org/static/planets.json")
        .then(function (response) {
            return response.json();
        });

Now, there is one last thing... async/await

You may have noticed writing out Promises and then and catch is pretty wordy.... well, you can write async functions in JS:

async function() {
  // you can now use the await keyword in this block
}

For example:

function() {
  delay(1000, () => "OK!")
    .then(result => console.log(result));
}

Could be written as:

async function() {
  let result = await delay(1000, () => "OK!");
  console.log(result);
}

Remember You can only use await inside of an async function.... it does nothing otherwise.

Another thing to remember is async functions always return a Promise....

async function() {
  return 5;
}

Is actually:

function() {
  return new Promise((resolve, reject) => resolve(5));
}

This means if you had:

let cached;
async function tryToGetJSON() {
  if(cached) return cached;
  else {
    let result = await fetch("https://my-website.com/some-api-endpoint-that-returns-json").json();
    cached = result;
    return result;
  }
}

You actually have:

function tryToGetJSON() {
  if(cached) return new Promise((resolve, reject) => resolve(cached));
  else {
    return new Promise((resolve, reject) => {
      fetch("https://my-website.com/some-api-endpoint-that-returns-json")
        .then(response => {
          let json = response.json();
          cached = json;
          resolve(json)
        })
        .catch(error => {
          reject(error);
        })
    })
  }
}

And that is ES6 Promises and async/await in a nutshell

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment