Callbacks, Promises, and Fetch

There are 2 primary ways of dealing with asynchronous behavior in JavaScript: callback functions and promises.

Callbacks

Timeouts

setTimeout(() => {
  console.log("Hi!");
}, 1000);

Geolocation

Run the following in the Chrome console. If you're on a Mac, you might get a warning saying that you need to enable Location Services. Do so, and try again.

const successHandler = (position) => {
  console.log(position.coords.latitude, position.coords.longitude);
};

const errorHandler = (error) => {
  console.error(error);
};

const options = {};

navigator.geolocation.getCurrentPosition(successHandler, errorHandler, options);

Promises

Suppose I make a promise to wash the dishes today. At this moment, I'm not able to wash them.

My promise can be in the following 3 states:

  1. Pending: I haven't washed the dishes yet, but the day isn't over!
  2. Fulfilled: I successfully washed the dishes today.
  3. Rejected: I failed in fulfilling my promise and didn't wash the dishes today.

In more technical terms:

"A Promise is an object representing the eventual completion or failure of an asynchronous operation. Essentially, a promise is a returned object you attach callbacks to, instead of passing callbacks into a function."

AJAX Requests with jQuery

const promise = $.ajax({ type: "GET", url: "/api/emails" });

// pending

promise.then(
  (emails) => {
    // fulfilled
  },
  (error) => {
    // rejected
    // this will happen for responses with an HTTP status code
    // in the 400 - 599 range
  }
);

The above can also be written like:

$.ajax({ type: "GET", url: "/api/emails" }).then(
  (emails) => {
    // fulfilled
  },
  (error) => {
    // rejected
  }
);

// pending

jQuery also offers $.getJSON as a shorthand:

const promise = $.getJSON("/api/emails");

promise.then(
  (emails) => {
    // fulfilled
  },
  (error) => {
    // rejected
  }
);

Creating a Fulfilled Promise

const promise = Promise.resolve(5);

promise.then((value) => {
  console.log(value); // 5
});

Creating a Rejected Promise

const promise = Promise.reject("Uh oh!");

promise.then(
  () => {
    // does not execute
  },
  (error) => {
    console.error(error); // Uh oh!
  }
);

A Promise Chain

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    return 2;
  })
  .then((value) => {
    console.log(value); // 2
    return 3;
  })
  .then((value) => {
    console.log(value); // 3
  });

A Promise Chain That Returns a Fulfilled Promise

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    return Promise.resolve(2);
  })
  .then((value) => {
    console.log(value); // 2
  });

A Promise Chain That Returns a Rejected Promise

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    return Promise.reject("Uh oh!");
  })
  .then(
    (value) => {
      // doesn't execute
    },
    (error) => {
      console.error(error); // Uh oh!
    }
  );

The above can also be written using catch:

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    return Promise.reject("Uh oh!");
  })
  .catch((error) => {
    console.error(error); // Uh oh!
  });

A Promise Chain That Throws An Error

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    throw new Error("Uh oh!");
  })
  .then(
    (value) => {
      // doesn't execute
    },
    (error) => {
      console.error(error); // Uh oh!
    }
  );

The above can also be written using catch:

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    throw new Error("Uh oh!");
  })
  .catch((error) => {
    console.error(error); // Uh oh!
  });

Recovering From an Error

Promise.resolve(1)
  .then((value) => {
    console.log(value); // 1
    throw new Error("Uh oh!");
  })
  .catch((error) => {
    console.error(error); // Uh oh!
    return "Everything will be ok";
  })
  .then((value) => {
    console.log(value); // Everything will be ok
  });

Creating a Promise with the Promise class

const promise = new Promise((resolve, reject) => {
  resolve(5);
});

promise.then((value) => {
  console.log(value); // 5
});
const promise = new Promise((resolve, reject) => {
  reject("Uh oh!");
});

promise.then(
  () => {
    // does not execute
  },
  (error) => {
    console.error(error); // Uh oh!
  }
);

Exercise

Create a function called timeout that behaves like setTimeout but instead returns a promise that fulfills after the duration in milliseconds has passed. For example:

timeout(2000).then(() => {
  console.log("2 seconds have passed!");
});

Solution

Handling Multiple Promises

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);

Promise.all([promise1, promise2]).then((array) => {
  console.log(array); // [1, 2]
});
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject("Uh oh!");

Promise.all([promise1, promise2]).then(
  () => {
    // does not execute
  },
  (reason) => {
    console.log(reason); // Uh oh!
  }
);

AJAX Requests with fetch

fetch("https://pokeapi.co/api/v2/pokemon?limit=10")
  .then((response) => {
    // this will get called for responses regardless of the HTTP status code
    console.log("HTTP Status Code", response.status);
    return response.json();
  })
  .then(
    (data) => {
      console.log(data);
    },
    (error) => {
      // this will get called if the response
      // can't be parsed as JSON
    }
  );