Article summary
If you’ve built out any substantial application using JavaScript, chances are you’ve needed to perform asynchronous operations. Since JavaScript is a single-threaded language, async utilities like callbacks and Promises allow for long-running operations like network requests to execute asynchronously (that is, without blocking the main thread).
When reviewing some legacy code, I found myself needing a refresher on the difference between JavaScript callbacks and Promises. I also wanted to convert some callbacks into Promises. Here’s what I learned.
What is a callback?
A callback is a function passed as an argument to another function to be invoked at some point in time.
Callbacks are often used when a function needs to be invoked after performing asynchronous operations. For instance, if I wanted to fetch some data and then do something with the fetched data, I could define a function fetchData
that calls one callback on success and a different callback on error.
function fetchData(dataToFetch, successCallback, failureCallback){
// fetches the data
// success? invoke the success callback with the fetched data
successCallback(data)
//failure? invoke the failure callback with the error
failureCallback(data)
}
function successCallback(data){
console.log("got the data!",data)
}
function failureCallback(e){
console.log("failed with an error!", e)
}
// to execute
fetchData('thing1', successCallback, failureCallback)
An example of a function that takes an asynchronous callback is setTimeout
. Set timeout takes a function to be executed (the callback function) after waiting a specified number of milliseconds (the async operation).
Let’s define a callback function sayHello
.
const sayHello = () => {console.log("hi")}
If we pass the setTimeout
function to the sayHello
callback function as the first parameter and 3,000 as the second parameter, the sayHello
function will be invoked after 3,000 milliseconds:
setTimeout(sayHello, 3000)
What is a Promise?
According to JS docs, a Promise is “an object representing the eventual completion or failure of an asynchronous operation.”
Say we want to fetch some data from another system and then print out the data. We could create a Promise — let’s call it fetchedData
— to represent either the data returned from the eventual successful completion of fetching data or a failure (maybe the network is down). We don’t know if the value is going to resolve to the fetched data or be a failure state, but we do know fetchedData
will eventually return a completion with some data or a failure with an error that we can do something with.
Here’s how we could create Promise to fetch the data.
const fetchedData = new Promise((resolve, reject) => {
// attempt to fetch some data
fetch("http://the-best-data").then((res) => {
//successfully got data
resolve(res)
})
.catch((e) => {
//an error occurred while fetching data
reject(e)
});
})
And here is how we could use that Promise:
fetchedData.then(
(data) => {console.log("great success!")} // what to do in the success case,
(data) => {console.log("great failure!")} // what to do in the failure case
)
Converting a Callback to a Promise
Maybe you have a function that takes a callback but you would prefer to work with a Promise.
Say you have a function with callbacks that looks like this:
function getTheThing(thingToGet, successCallback, failureCallback){
// get the thing
// on success
successCallback(thing)
// on failure
failureCallback(thing)
}
We can create a function getTheThingAsync
that creates and returns a Promise. We call the Promise’s resolve function in the success callback and call the Promise’s reject function in the failure callback. So, if getTheThing
succeeds, we’ll be calling the Promise’s resolve with the thing we just got. Otherwise, on failure, we’ll be calling the Promise’s reject with the error message.
function getTheThingAsync(thingToGet){
return new Promise((resolve, reject) => {
getTheThing(thingToGet, thing => {
resolve(thing)
}, error => {reject(error)}
})
}
We can use the new Promise like this:
getTheThingAsync('thingToGet').then((res) => {console.log("res is", res}).catch(e => {console.log("Got an error!",e)})
Additionally, keep in mind that if you are using Node, you can use util.promisify
to convert a callback-based function to a Promise-based one.