Hello Stackoverflow users,
Many peoples like me searched for how to pass extra arguments to a callback function. The questions have similar titles but actually they have different challenges and many ways to solve. Plus, it is always a pleasure to share practices to be more experienced.
Recently, I faced a pretty simple challenge in my node js project. One of the APIs I communicate with has an SDK that works synchronically. And I used to pass callback functions every time (which is annoying when you have requests depending on each other and some data needs to transfer within the app layers).
Imagine a plan payment flow that goes like this, a client sends a request to the server including the selected plan and his ID. When the server API layer receives the request data, it passes it to a third-party service function ( .create(...)
). The third-party service function receives a callback with 2 parameters function(err, plan_document)
. And then, the callback is supposed to apply the selected plan logic on the client by the ID in the request.
** We need to pass the client's and the plan's data to the callback function to apply the logic. The third-party service provides to the callback a plan_document parameter and we still need to somehow pass the client id from the API layer to the service.
The code will look like this.
const create_plan_agreement = (req, res) => {
// some code
var client_id = req.auth.client_id;
third_party.plan_agreement.create({}, update_plan_agreement);
};
const update_plan_agreement = (err, plan_document, client_id) => {
/*
The third-party `third_party.plan_agreement.create` function passes the first
two parameters and somehow we need to add the client_id
*/
console.log('client plan activated');
active_client_plan(plan_document, client_id);
};
------------------ EDIT ------------------
I wonder what if the flow was longer and I need the client id farther than the update function like this.
const create_plan_agreement = (req, res) => {
// some code
var client_id = req.auth.client_id;
third_party.plan_agreement.create({}, update_plan_agreement);
};
const update_plan_agreement = (err, plan_document) => {
console.log('plan activated, send notification to the client');
third_party.plan_agreement.update(plan_document, send_agreement_notification);
};
const send_agreement_notification = (err, plan_document) => {
console.log('client plan activated');
active_client_plan(plan_document, this.client_id);
};
What should I do in this case? Should I keep repeating the .bind({'client_id': client_id})
function until the last step in the flow?
If you want to support older people, you can easily bind using a containing callback, like this:
const create_plan_agreement = (req, res) => { // some code var client_id = req.auth.client_id; third_party.plan_agreement.create({}, function(params, from, create) { update_plan_agreement(params, from, create, client_id) }); }; const update_plan_agreement = (err, plan_document, client_id) => { /* The third-party `third_party.plan_agreement.create` function passes the first two parameters and somehow we need to add the client_id */ console.log('client plan activated'); active_client_plan(plan_document, client_id); };
The traditional way is to use a closure. Define the functions inside the parent's scope so that they can access the client_id
as an enclosed variable (kind of like global variables):
const create_plan_agreement = (req, res) => {
// some code
var client_id = req.auth.client_id;
const update_plan_agreement = (err, plan_document) => {
console.log('plan activated, send notification to the client');
third_party.plan_agreement.update(plan_document, send_agreement_notification);
};
const send_agreement_notification = (err, plan_document) => {
console.log('client plan activated');
// Note: this function can access client_id
// because it is in scope
active_client_plan(plan_document, client_id);
};
third_party.plan_agreement.create({}, update_plan_agreement);
};
Closures are to scopes what objects are to classes. A closure is an instance of a scope. So unlike regular global variable, each call to create_plan_agreement()
will create its own closure with its own copy of client_id
.
With modern javascript it is often easier to handle this with Promise
s. Convert legacy functions to return a Promise
and then you can use async/await
:
const create_plan_agreement = async (req, res) => {
// some code
var client_id = req.auth.client_id;
try {
var plan_document = await plan_agreement_create({});
var updated_plan_document = await update_plan_agreement(plan_document);
send_agreement_notification(updated_plan_document, client_id);
}
catch (err) {
// handle errors here.
}
};
const plan_agreement_create = (arg) {
return new Promise ((ok, fail) => {
third_party.plan_agreement.create({}, (err, result) => {
if (err) {
return fail(err);
}
ok(result);
});
})
}
const update_plan_agreement = (plan_document) => {
return new Promise ((ok, fail) => {
third_party.plan_agreement.update(plan_document, (err, result) => {
if (err) return fail(err);
ok(result);
});
});
};
const send_agreement_notification = (plan_document, client_id) => {
active_client_plan(plan_document, client_id);
};
Or even without async/await
Promise
s still make callbacks easier to use:
const create_plan_agreement = async (req, res) => {
// some code
var client_id = req.auth.client_id;
plan_agreement_create({})
.then(doc => update_plan_agreement(doc));
.then(doc => {
send_agreement_notification(doc, client_id)
})
.catch(err => {
// handle errors here.
});
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.