JavaScript: Promises

JavaScript: Promises

What is a JavaScript promise?

JavaScript Promises let you write code that may eventually resolve to a value that you don't know yet. I say may because a value isn't guaranteed. You might want to throw an error. Keep reading to learn more.

function handleResult(result) {
  console.log(result);
}

getResult().then((result) => { 
  handleResult(result);
});

Here we call a function named getResult which returns a Promise. To get the result we call then on the promise that's returned by getResult and pass it a callback function. The callback function you pass into the then function isn't guaranteed to run right away, or ever really. But if the getResult function has any data that it wants to get back to the caller, it'll be sent in a way that will invoke that callback function.

So what does getResult look like?

function getResult() {
  return new Promise((resolve) => {
    resolve("value");
  });
}

Here's an example of what getResult could look like. This isn't a really helpful function though, right? But let's go through what it does anyway.

The getResult function returns a Promise, we expected that much. The Promise it returns is instantiated with a callback function that accepts a resolve argument, but why? The resolve argument is how the Promise can return data to the caller. When you invoke resolve inside the Promise callback the then function is invoked above. You now have a very good basic understanding of Promises.

What about errors?

I intentionally left out error handling for simplicity, but let's see how that works with a Promise.

function handleError(error) {
  console.error(error);
}

function handleResult(result) {
  console.log(result);
}

getResult().then((result) => { 
  handleResult(result);
}).catch((error) => {
  handleError(error);
});

Notice I added a catch call above and passed it a callback function. Something to note here, we chained the calls, the reason we can do that is because the then function returns the original Promise that getResult returned as well. The following code is equivalent, but longer.

const promise = getResult();
promise.then((result) => { 
  handleResult(result);
});
promise.catch((error) => {
  handleError(error);
});

Now the last thing to know is how a Promise invokes the catch function.

function getResult() {
  return new Promise((resolve, reject) => {
    reject("something went wrong");
  });
}

Bringing it all together

Here's an example of a function that let's you pick if a Promise resolves (calls then) or is rejected (calls catch)

function getResult(shouldReject) {
  return new Promise((resolve, reject) => {
    if(shouldReject) {
      reject("was told to reject");
    } else {
      resolve("resolving...");
    }
  });
}

// this chain will only ever call `then`
getResult().then((result) => { 
  console.log(result);
}).catch((error) => { 
  console.error(error);
});

// this chain will only ever call `catch`
getResult(true).then((result) => { 
  console.log(result);
}).catch((error) => { 
  console.error(error);
});

How does this compare with using async/await?

async/await and Promise are interchangeable.

function getResult(shouldReject) {
  return new Promise((resolve, reject) => {
    if(shouldReject) {
      reject("was told to reject");
    } else {
      resolve("resolving...");
    }
  });
}

// this will never call the catch block
try {
  const result = await getResult();
  console.log(result);
} catch(error) {
  console.error(error);
}

// this will always execute the catch block
try {
  const result = await getResult(true);
  console.log(result);
} catch(error) {
  console.error(error);
}

The code above works with the same getResult function from earlier. That's because async/await and Promises are compatible. Here's another example, notice the the getResult function is now async, but the code that uses it is using then/catch.

async function getResult(shouldReject) {
    if(shouldReject) {
      throw "was told to reject";
    } else {
      return "resolving...";
    }
}

// this chain will only ever call `then`
getResult().then((result) => { 
  console.log(result);
}).catch((error) => { 
  console.error(error);
});

// this chain will only ever call `catch`
getResult(true).then((result) => { 
  console.log(result);
}).catch((error) => { 
  console.error(error);
});

What does all this have to do with asynchronous code?

All the previous examples resolved or rejected a value instantly. This isn't very useful, you could have just returned the value instead of a Promise. So how can this be used in a more asynchronous way? Here's an example of using XMLHttpRequest to fetch some data. This example is incomplete, but it shows how you could use a Promise to eventually return a value request.response or possibly reject the Promise and return an error.

function fetchData(url) {
  return new Promise((resolve, reject) => {
    var request = new XMLHttpRequest();
    request.onreadystatechange = () => {
      if (request.readyState === 4) {
        resolve(request.response);
      }
    };
    request.addEventListener("error", (e) => {
      reject(e);
    });
    request.open("GET", url);
    request.send();
  });
}

fetchData("public/test.txt")
  .then((data) => console.log(data))
  .catch((e) => console.error(e));