Skip to main content

Debugging Asynchronous Lifecycle Methods

Various lifecycle methods (see: Gatsby Node APIs) within Gatsby are presumed to be asynchronous. In other words, these methods can eventually resolve to a value and this value is a Promise. We wait for the Promise to resolve, and then mark the lifecycle method as completed when it does.

In the context of Gatsby, this means that if you are invoking asynchronous functionality (e.g. data requests, graphql calls, etc.) and not correctly returning the Promise an internal issue can arise where the result of those call(s) happens after the lifecycle method has already been marked as completed. Let’s consider an example:

gatsby-node.js
exports.createPages = async function({ actions, graphql }) {
graphql(`
{
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`).then(res => {
res.data.allMarkdownRemark.edges.forEach(edge => {
const slug = edge.node.fields.slug
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/post.js`),
context: { slug },
})
})
})
}

Can you spot the error? In this case, an asynchronous action (graphql) was invoked but this asynchronous action was neither returned nor awaited from createPages. This means that the lifecycle method will be marked as complete before it’s actually completed—which leads to missing data errors and other hard-to-debug errors.

The fix is surprisingly simple—just one line to change!

gatsby-node.js
exports.createPages = async function({ actions, graphql }) {
await graphql(`
{
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`).then(res => {
res.data.allMarkdownRemark.edges.forEach(edge => {
const slug = edge.node.fields.slug
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/post.js`),
context: { slug },
})
})
})
}

Best Practices

Use async / await

With Node 8, Node is able to natively interpret async functions. This lets you write asynchronous code as if it were synchronous! This can clean up code that previously was using a Promise chain and tends to be a little simpler to understand!

Use Promise.all if necessary

Promise.all wraps up multiple asynchronous actions and resolves when each have completed. This can be especially helpful if you’re pulling from multiple data sources or abstracted some code that returns a Promise into a helper. For instance, consider the following code:

gatsby-node.js
const fetch = require("node-fetch")
const getJSON = uri => fetch(uri).then(response => response.json())
exports.createPages = async function({ actions, graphql }) {
const [pokemonData, rickAndMortyData] = await Promise.all([
getJSON("https://some-rest-api.com/pokemon"),
getJSON("https://some-rest-api.com/rick-and-morty"),
])
// use data to create pages with actions.createPage
}

Edit this page on GitHub
Docs
Tutorials
Plugins
Blog
Showcase