How to Write Callbacks in Node JS Airtable Lookup for Beginners Easy Guide
I started learning Node.js recently, and I was disappointed with how difficult and technical the supposed “basic” guides online to learn Callbacks are.
- https://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/
- https://stackoverflow.com/questions/3458553/javascript-passing-parameters-to-a-callback-function
- https://zellwk.com/blog/nested-callbacks/
- https://www.jstips.co/en/javascript/passing-arguments-to-callback-functions/
- https://www.pluralsight.com/guides/javascript-callbacks-variable-scope-problem
- https://medium.com/@fotios.floros/explaining-javascript-callbacks-3d5a9ad52819
Things like setTimeout, Promises, foo, “promises are the same in real life” all this other language was very confusing to someone who’s a beginner.
So let me start with the basics.
Why do we need callbacks?
If you’re like me, you’re used to writing synchronous code, meaning that if you want function 2 to take in data from function 1, you should write function 1 first, have it calculate a variable, and then function 2 will run using that variable.
Synchronous code: const airtable = require('airtable');//Lookup the courses you're enrolled in based on your name
function fetchCoursesByName(name) {//show the code that operates here
return coursesArr;}//returns an array of your schedule on a day
function fetchScheduleByDay(courses, day) {//show the code that operates here
return scheduleArr;}testFetchScheduleByDay() { var name = "Bob";
var day = "Wednesday";
var courses = fetchCoursesByName(name);
var schedule = fetchScheduleByDay(courses, day);
console.log("Your schedule on " + day + " is" + schedule);}testFetchScheduleByDay();
The above code will NOT work on Node.js. The reason is that if you’re calling to a database, or outside source, it’s possible that the call to fetch your list of courses (fetchCoursesByName) won’t finish before you try to lookup your schedule by day (fetchScheduleByDay). In fact because Node JS basically reads down and doesn’t wait for one function to finish before moving onto the next line, this will always return:
Your schedule on Wednesday is undefined
Well that’s not very useful if you’re trying to get your schedule for the day.
Let’s take an example database
Imagine we’d like to look up data from two tables. The first is a list of names with the lectures a student is enrolled in.
The second table is a list of lectures and the times they occur during the week.
The goal of the code is to input a name and a day, i.e. “Bob”, “Wednesday”, and have it return Bob’s schedule on Wednesday. Let’s assume that the code is written to also sort the lectures so they’re in the right time order.
input:
output:
The purpose of such a program, is if you’re a forgetful student, and you can’t remember your classes for the day, you could message a service hosting this code and it would respond with your day’s schedule. Something similar could be extended for anything where you need to lookup data given an input.
Now that you have an idea about what we’d like to use Node.js to do, let’s talk about callbacks.
Here’s how to write this for Node.js
To turn the code at the beginning of the article into asynchronous code, you have to add the word callback to all of your functions.
Now there’s all sort of confusing things on the Internet for how to write this code, so I’m going to try to make it as simple as possible.
Going to take this one piece at a time. To make a function “callback friendly” you need to do two things.
- First, add a parameter named “callback” into the the function.
- Then, change your return statement from “return coursesArr”, to “callback(coursesArr)”.
Asynchronous code:const airtable = require('airtable');//Lookup the courses your enrolled in based on your name
function fetchCoursesByName(name, callback) {//show the code that operates here
callback(coursesArr);}
What you’re telling Node.js to do in that return statement is to basically come back to the function fetchCoursesByName once it’s ready, and use the value it ultimately calculated.
Because we are calling an external database, we need to do the same thing on this second function,
<fetchScheduleByDay>
you’re going to add the word callback as a parameter, and replace the word return inside the function with the word callback.
asynchronous code //returns array of your schedule on a Day
function fetchScheduleByDay(courses, day, callback) {//show the code that operates herecallback(scheduleArr);}
Below, you can compare and contrast the difference.
All we did was add the word “callback” as an additional parameter, and replace the word “return” for the word callback.
Now, we have to write the code that actually runs through these functions. We want to first take the student’s name, and return a list of their courses. Then we want to lookup the list of their courses based on a day that they specify.
Now we have to write the cod that actually runs through these functions. We want to first take the student’s name and return a list of their courses. Then, we want to look up their schedule based on a day they specify. We don’t need their major for anything (top function), but I’m including it at the top to show callbacks with three functions in sequence.
function testCode(name, day)
{fetchMajorbyName(name, (major) => {
console.log('Your major is ' + major);
fetchCoursesByName(name, (courses) => {
console.log('Your courses are ' + courses);
fetchScheduleByDay(courses, day, (schedule) => {
console.log('Your schedule is ' + schedule);
return schedule;
});
});
});
So this code will work. This is telling Node to go one at a time, and don’t go to the next function until you finished the prior one. So this way you can run your functions first, second, then third.
The syntax is here that whatever you put in the parenthesis is the name of the thing you want to return. So you’re saying return the variable “courses” as the thing that outputs when you call the fetchCoursesByName function.
You could just as easily name those things within the parenthesis as whatever you want. This code below runs the same:
function testCode(name, day)
{fetchMajorbyName(name, (major) => {
console.log('Your name is ' + name);
fetchCoursesByName(name, (uggabugga) => {
console.log('Your courses are ' + uggabugga);
fetchScheduleByDay(uggabugga, day, (schedule) => {
console.log('Your schedule is ' + schedule);
return schedule;
});
});
});
The parameter uggabugga here will return whatever fetchCoursesByName outputs. We know that fetchCoursesByName will output the list of courses for the student, so that’s the value that will go in uggabugga. Basically whatever you write as that last parameter is the thing that’s going to be passed on forward.
Think you’re done? I thought so too. This should work locally on your computer.
But if we want to call this function externally, from another service, and we want to call it from another function, we’re going to be in trouble and it will return undefined. I don’t really know why but I think it’s because it’s going to run through your nested functions here before it waits for all these callbacks to come back. So you have to instruct your other function to wait until you get all the data before moving forward. In order to do this, we need to do two things. First, we need to redo our testCode function to be a callback as well.
function testCode(name, day, callback)
{fetchNamebyId(id, (name) => {
console.log('Your name is ' + name);
fetchCoursesByName(name, (uggabugga) => {
console.log('Your courses are ' + uggabugga);
fetchScheduleByDay(uggabugga, day, (schedule) => {
console.log('Your schedule is ' + schedule);
callback(schedule);
});
});
});
All I did here was add the parameter “callback” to the top, and instead of a return statement, use “callback(schedule);” to return the data that comes out out of fetchSchedulebyDay.
Now that testCode is a callback function, we can call it from another function and it will return the right information. The second thing to do is to write the function that calls it as a Promise, so it waits until testCode is finished before moving forward.
The tool I’m working with says the function to execute the program has to be called “main” and can only take in one thing called “params”. So those are the restrictions that I’m going to be working with. Now I add this one final piece of code in order to call testCode above and make sure it returns the right thing.
function main(params) {
return new Promise((resolve, reject) => {
params.name = "Bob"
params.day = "Wednesday";
testCode(params.name, params.day, (schedule)=> {
resolve(schedule);
});
}).then((data)=>{
console.log("The promise has been resolved");
return data;
});
}
You’ll see above this notation is different, we have to use something called a Promise, which is related to a callback. I don’t know exactly how they’re related, but the code above will basically wait until the testCode runs and returns the schedule, before moving to the “Then” part.
[will need to figure out how to add error to show the “reject” part]
The syntax here is that the thing that I return from the first part of the promise is written as resolve(schedule). I think this means that resolve is also a callback, but I could be wrong. Anyway, you want to use the word resolve(schedule) to return the thing out of the first part of the promise.
testCode(name,params.day,(schedule)=>{
resolve(schedule);
});
Moving on to the “Then” part is the thing you ultimately want to return. The syntax here is basically what you got from the first part in parenthesis, and you return that.
}).then((data)=>{
console.log("The promise has been resolved");
return data;
});
You can also return it as a JSON object which is what I have to do for the program that I’m working with. So in order to return the function as json simply change “return data” above to
}).then((data)=>{
console.log("The promise has been resolved");
return {result:data};
});
This will return a JSON with one name-value pair, with “result” being the name and “data” being the value. So this will return to your program:
{"result" : ["Art History 9:00 - 9:50", "Calculus 11:00 - 11:50", "Sociology 1:00 - 1:50"]}
Now you can call your main(params) function and you can pass it whichever day you want. It could be ({“day”:“Monday”}) or ({“day”:“Thursday”}) and that will run the same code
You can pass main two parameters and it will do the same thing. ({“day”:“Tuesday”, “name”:“Lisa”}) and it will return the Tuesday schedule for Lisa Monroe.
I haven’t put the Github yet up with all this code, but I will soon. Hope that helps!
Thanks to Jordan Soltman for his invaluable advice in explaining the basics of callbacks to me when the Internet and many hours and days of searching failed to make this clear.
FAQ
What if my function originally didn’t take any parameters?
You would use the same syntax if you had a function that didn’t take any parameters, by adding a parameter.
synchronous version fetchAllCourses() {//code that returns a list of all courses at the Universityreturn listofCourses;}asynchronous version fetchAllCourses(callback) {//code that returns a list of all courses at the Universitycallback(listofCourses);}
Basically use the word “callback” in place of return and make sure you add it as a parameter.
When do you need to make functions callback friendly?
I’m not really sure the answer but I think the answer is anytime you are making a call to an external service, even if it’s a very quick call, because there will be some unknown amount of processing time.
How do I know if a function is using a callback?
I look for the weird notation that uses a parenthesis followed by a curly bracket and a semi-colon. If I see that somewhere, I know the part of the code is making a call to something external, and therefore I’ll need to make my function “callback friendly”.
)};
Why do I need to use a Promise?
If you’re calling the function that runs your code from an external service, i.e. AWS Lambda or IBM Cloud Functions, you need to tell your program to wait until to have data before returning. You can’t just call your function that runs the code. You need to call your function via a Promise, and the function must be made into a callback, so everything knows to wait to have data before returning. The code earlier in the write-up illustrates the Promise part as well as turning textCode into a callback.
Sandbox area
Asynchronous code:const airtable = require('airtable');//Lookup the courses your enrolled in based on your name
function fetchCoursesByName(name, callback) {//show the code that operates here
callback(coursesArr);}//returns array of your schedule on a day
function fetchScheduleByDay(courses, day, callback) {//show the code that operates here
callback(scheduleArr);}function testFetchScheduleByDay() {
var name = "Bob";
var day = "Wednesday";
fetchCoursesByName(name, (courses) => {
console.log('Your courses are ' + courses);
fetchScheduleByDay(courses, day, (schedule) => {
console.log('Your schedule is ' + schedule);
return schedule;
});
});}testFetchScheduleByDay();
This is everything. With the Promise and testCode as a callback:
Asynchronous code:const airtable = require('airtable');//Lookup the courses your enrolled in based on your name
function fetchCoursesByName(name, callback) {//show the code that operates here
callback(coursesArr);}//returns array of your schedule on a day
function fetchScheduleByDay(courses, day, callback) {//show the code that operates here
callback(scheduleArr);}function testCode(name, day, callback)
{ fetchMajorbyName(name, (major) => {
console.log('Your name is ' + name);
fetchCoursesByName(name, (uggabugga) => {
console.log('Your courses are ' + uggabugga);
fetchScheduleByDay(uggabugga, day, (schedule) => {
console.log('Your schedule is ' + schedule);
callback(schedule);
});
});
});
}
function main(params) {
return new Promise((resolve, reject) => {
params.name = "Bob"
params.day = "Wednesday";
testCode(params.name, params.day, (schedule)=> {
resolve(schedule);
});
}).then((data)=>{
console.log("The promise has been resolved");
return data;
});
}