Promise Pending with Airtable API in Node.js

I’m developing a project using the Airtable API with Node.js, and I have two tables: one for mentors and another for jobs.

Here’s what I’m trying to achieve:

  • Fetch the mentor data from the first table
  • For every mentor, look up their job information from the jobs table
  • Render this information on my webpage.

However, I’m running into a problem where I get a promise that is pending and not resolving. Below is the code I am currently using:

router.get('/', function(req, res, next) {
  const title = 'test';
  
  const getRecords = async () => {
    const records = await mentors
      .select({maxRecords: 6, view: "Grid view"})
      .eachPage(function page(records, fetchNextPage) {
        records.forEach(function(record) {
          jobs.find(record.fields["Quel est votre métier actuel"], function(err, jobRecord) {
            if (err) { 
              console.error("An error occurred", err); 
              return; 
            }
            record.fields.job = jobRecord.fields.Job;
            return record;
          });
        });
        fetchNextPage();
      });
  };
  
  getRecords()
    .then(function(records) {
      console.log(records);
      res.render('index', {title: title, records});
    });
});

What can I do to resolve this issue and obtain the correct records?

You’re mixing async/await with callbacks, and eachPage isn’t returning the records you expect.

I’ve hit this exact problem building internal tools that pull from multiple Airtable bases. Your jobs.find() is async but you’re not waiting for it to finish.

Here’s my fix:

router.get('/', async function(req, res, next) {
  const title = 'test';
  
  try {
    // Get all mentor records first
    const mentorRecords = await mentors
      .select({maxRecords: 6, view: "Grid view"})
      .all();
    
    // Add job data to each mentor
    const enrichedRecords = await Promise.all(
      mentorRecords.map(async (record) => {
        try {
          const jobRecord = await new Promise((resolve, reject) => {
            jobs.find(record.fields["Quel est votre métier actuel"], (err, jobRecord) => {
              if (err) reject(err);
              else resolve(jobRecord);
            });
          });
          
          return {
            ...record,
            fields: {
              ...record.fields,
              job: jobRecord.fields.Job
            }
          };
        } catch (err) {
          console.error('Error fetching job for mentor:', err);
          return record; // Return mentor without job data if lookup fails
        }
      })
    );
    
    res.render('index', {title, records: enrichedRecords});
  } catch (error) {
    console.error('Error:', error);
    res.status(500).send('Server error');
  }
});

Key changes:

  1. Use .all() instead of .eachPage() to get actual records
  2. Wrap jobs.find in a Promise since it’s callback-based
  3. Use Promise.all() to wait for all job lookups
  4. Handle errors properly

This works great in production when you need to merge data from multiple sources.

The problem is eachPage doesn’t return a promise with your records - it just handles pagination. Hit this same issue with Airtable’s API last year.

Your async operations in the forEach loop aren’t being awaited, so the function finishes before job lookups complete. That’s why you’re getting a pending promise.

Ditch eachPage and use firstPage() or all() to actually get records back. Then handle the async job lookups properly. Since jobs.find() is callback-based, you’ll need to promisify it or use a library that converts callbacks to promises.

What worked for me: collect all mentor records first, then use Promise.allSettled() for job lookups in parallel. This way you won’t lose mentors if some job lookups fail - happens more than you’d think with external APIs.

Don’t forget error handling - Airtable rate limits will bite you.