Debugging Asynchronous Lifecycle Methods

For Gatsby developers who are used to previous paradigms of JavaScript programming, the introduction of specific syntax for asynchronicity into JavaScript may be a rude awakening. As of Node.js v8, Node.js can natively handle the async and await keywords, which are used to identify functions that should be treated asynchronously but are written as if they were synchronous. The async and await keywords are particularly useful for situations where consecutive Promises were chained together in older JavaScript, which often led to confusing and hard-to-read code.

Within Gatsby, certain lifecycle methods, like createPages in Gatsby’s Node APIs, are presumed by the framework to be asynchronous. This means it is generally assumed that a value may not immediately be available upon the execution of the createPages logic. Methods like createPages will eventually resolve to a value (or not resolve, throwing an error) represented by a Promise. In asynchronous JavaScript with Promises, we wait for the Promise to resolve to a usable value, and in Gatsby’s case, the lifecycle method in question (such as createPages) is considered complete once the Promise resolves.

Gatsby’s Node APIs handle a great deal of asynchronous logic, such as requests for data from external sources, invocations of graphql, and more. For this reason, Gatsby developers may encounter difficult-to-debug errors when writing logic in gatsby-node.js that does not correctly return Promises. The resolutions of these promises may occur after the lifecycle method is considered by Gatsby to be complete, thus returning a value that was expected but now is never used.

When writing logic in gatsby-node.js, pay close attention to whether a method is expected to be asynchronous or not. For instance, in the following example, not including the await keyword shown ahead of the invocation of graphql would result in absent data, since createPages will not wait for the value representing the result of graphql to be returned:

// 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/article.js`),
        context: {
          slug
        },
      })
    })
  })
}

There are also scenarios where it is necessary to wait for a series of unconnected Promises to resolve before proceeding. In JavaScript, the Promise.all() method encapsulates multiple asynchronous actions, such as a data fetch (e.g., fetch().then()) that represents a Promise but needs to query multiple APIs or data sources. Consider the following example, in which we retrieve data from multiple APIs:

// gatsby-node.js
const fetch = require("node-fetch")

exports.createPages = async function ({ actions, graphql }) {
  const [siteAData, siteBData] = await Promise.all([
  ])

  // Further logic with actions.createPage.
}

Because one of the fetch operations may conclude before the other, we need to ensure that Gatsby awaits the resolution of all Promises contained therein before proceeding and marking the createPages lifecycle method as complete.


Posted

in

,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *