问题
First, let me start with what I want to achieve. I want to query a table in Airtable that includes data from a linked table. Airtable returns the IDs of records in linked tables so I need to do a second lookup on each of those records to get the data I want (e.g. Name
). Ultimately, I want to return to the client the ID and the other fields from the linked record. Problem is, I'm failing to achieve this because of the asynchronous nature of the API and my lack of understanding.
The best I've been able to do so far is execute a bit of code in the done()
callback of their API. The problem is that this isn't scalable because it means I'll get myself into callback-hell. (At least, that's what I think I've been able to determine so far.)
Here's my code (FYI this is code to be run in an Azure Function):
var Airtable = require("airtable");
var airtableApiKey = process.env.airtableApiKey;
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
var base = new Airtable({
apiKey: airtableApiKey
}).base('appBASEID');
var resultObj;
var accumulator = [];
base('Products').select({
sort: [{
field: 'Identifier',
direction: 'asc'
}]
}).eachPage(function page(records, fetchNextPage) {
records.forEach(function (record) {
context.log('Retrieved ', record.get('Identifier'));
context.log('Retrieved ', record.get('Vendor'));
accumulator.push(record._rawJson);
// base('Vendors').find(record.get('Vendor')[0], function( err, record) {
// if (err) { context.error(err); return; }
// context.log(record.get('Name'));
// });
});
fetchNextPage();
}, function done(error) {
context.res = {
// status: 200, /* Defaults to 200 */
body: JSON.parse(JSON.stringify(accumulator))
};
context.done();
});
};
You can see that I've commented some lines in the .eachPage()
section because, as I learned while working on this, that code doesn't execute in the order I expected it to.
How can I foreach
through accumulator
and .find()
the records I need?
回答1:
It looks like the issue you are having is that the done
callback is executing before the find
calls in your forEach
can finish. forEach
might be blocking, but each find
call is not. So you continue to fetch more pages, and eventually fetch them all, before you've managed to successfully pull all linked records.
Here is the code that I wrote to manage this. Note that it's long and there's likely room for improvement. I tried to also take care of the cases where your linked field may have multiple elements and where you may have multiple linked field columns that you are interested in looking up.
var base = Airtable.base(base_id)
var table = base.table(table_name);
// create a map between the linked record columns in your table
// and the table that those linked record's point to
var linked_fields = {
'Local Column A': 'Foreign Table X',
'Local Column B': 'Foreign Table Y'
}
// manage all records and promises
var all_records = [];
var all_promises = [];
// cycle through all pages in our table
table.select().eachPage(function page(records, fetchNextPage) {
// for each record, go through each linked field and pull all associated linked records
var page = records.map((record) => {
// for each column, we want to check if the given record has any linked records
// if it does, then go ahead and perform a fetch in the foreign table
// linked record fields are a list because there can be multiple linked records
// so we have to loop through each ID
var record_promises = Object.keys(linked_fields).map((field) => {
if (record.fields[field] !== undefined) {
let t = base.table(linked_fields[field]);
var linked_promises = record.fields[field].map((foreign_record_id) => {
return t.find(foreign_record_id);
});
// wait for this record to get all of its linked fields
return Promise.all(linked_promises).then((values) => {
// for each linked field, we don't need all the extra Airtable SDK noise
// so just use the rawJSON structure from the foreign record
// but update both the rawJson and fields structures in the local record
values = values.map((v) => {
return v._rawJson;
});
record.fields[field] = values;
record._rawJson.fields[field] = values;
});
}
});
// wait for the record to finish updating all linked fields
// and then return the record
return Promise.all(record_promises).then(() => {
return record;
});
});
// we have to wait for all records in this page to get updated information
// we can use all_promises to track all of our active promises
all_promises.push(Promise.all(page));
// we don't need to wait for everything to resolve before fetching the next page
// and we probably don't want to wait.
// Airtable pagination will die if you wait too long in between calls
// and you have to start all over
fetchNextPage();
}, function done(error) {
if (error) {
reject(error);
}
// once we've fetched all pages, wait for all those pages to settle
// we will get a list of lists at the end of this, where each page is a different list
// so we can now flatten it into a single list by pushing all records into all_records
Promise.all(all_promises).then((results) => {
for (var i in results) {
all_records.push.apply(all_records, results[i]);
}
context.res = {
// status: 200, /* Defaults to 200 */
body: JSON.parse(JSON.stringify(all_records))
};
context.done();
}).catch((err) => {
// handle error response
});
});
来源:https://stackoverflow.com/questions/50108966/how-to-return-data-from-the-done-callback-when-working-with-airtable-js