Developers sometimes classify outlier functions used to maintain code and debug certain aspects of a program’s functionality as utility functions. Utility functions don’t necessarily create new functionality in a program, but you can think of them as internal tools used to maintain and debug your code. The Node.js util
core module contains methods specifically designed for these purposes. The util
module can be required into the file using:
const util = require('util');
Once required, you have access to many useful objects and methods within the util
module. One important object is types
, which provides methods for runtime type checking in Node.
const util = require('util'); const today = new Date(); const earthDay = 'April 22, 2022'; console.log(util.types.isDate(today)); console.log(util.types.isDate(earthDay));
In the above example, we first require in the util
module. Next, we declare two variables: today
stores today’s date as an instance of the Date
object, and earthDay
stores the date of Earth Day as a string. We then log the results of type checking each variable using util.types.isDate()
. The types.isDate()
method checks for Date
objects and returns a boolean value, giving us:
true false
Since today
is a Date
object, it returns true
, and since earthDay
is a string, it returns false
!
Another important util
method is .promisify()
, which turns callback functions into promises. As you know, asynchronous programming is essential to Node.js. In the beginning, this asynchrony was achieved using error-first callback functions, which are still very prevalent in the Node ecosystem today. But since promises are often preferred over callbacks and especially nested callbacks, Node offers a way to turn these into promises. Let’s take a look:
function getUser (id, callback) { return setTimeout(() => { if (id === 5) { callback(null, { nickname: 'Teddy' }) } else { callback(new Error('User not found')) } }, 1000) } function callback (error, user) { if (error) { console.error(error.message) process.exit(1) } console.log(`User found! Their nickname is: ${user.nickname}`) } getUser(1, callback) // -> `User not found` getUser(5, callback) // -> `User found! Their nickname is: Teddy`
Here we have a function that queries a database for a specified user ID. getUser
methods are very common in back-end applications, and most will also support error-first callbacks. Since a database query typically takes longer to run than other operations, we simulate the query with a setTimeout()
method that executes a callback function after 1000 milliseconds (or 1 second). If the user with the specified ID is found, the callback function is executed with null
passed in as the argument for the error
parameter, and an object containing the returned user information is passed in as an argument for the user
parameter. If the user is not found, the callback function is executed, passing in a new Error
as the argument for the error
parameter. A second argument for user
is not necessary since the function will end in the case of an error.
This way of handling this function may seem a bit convoluted these days, but with .promisify()
, we can easily change it into a modern, cleaner, and more maintainable version of itself:
const getUserPromise = util.promisify(getUser); getUserPromise(id) .then((user) => { console.log(`User found! Their nickname is: ${user.nickname}`); }) .catch((error) => { console.log('User not found', error); }); getUser(1) // -> `User not found` getUser(5) // -> `User found! Their nickname is: Teddy`
We declare a getUserPromise
variable that stores the getUser
method turned into a promise using the .promisify()
method. With that in place, we’re able to use getUserPromise
with .then()
and .catch()
methods (or we could also use the async...await
syntax here) to resolve the promise returned or catch any errors.
Now, this is an extremely simplified example, but it’s helpful to recognize how to use this important tool when you start working with more complex callback functions.
Instructions
In app.js we’ve required in an object containing long distance hiking trails in the US. You can view that object by opening the trails.js file from the navigator.
Below that is a callback function, getTrailDistance
, that’s ready to be converted using util.promisify()
.
Start by requiring the util
module at the top of the app.js, and saving it to a util
variable.
Next, below the callback function call at the bottom of the file, using const
, declare a variable, getTrailDistancePromise
, and store util.promisify(getTrailDistance)
.
Then, call the new promisified function, getTrailDistancePromise
, with 'North Country'
(or any trail property from the trails
object in trails.js) as the only argument. Resolve the promise with a .then()
method, and reject the promise with a .catch()
method.
Lastly, inside of then()
, log to the console:
`The ${nickname} is ${mi} miles long!`
where nickname
is the nickname and mi
is the distance of the passed-in trail. Inside catch()
, pass in a function that logs any errors.
Way to go! You just turned a dusty old callback function into a shiny new promise! You should see identical responses from the callback and the promise.