Binding a callback function when using Async Waterfall - javascript

I was trying for a while to figure out a good solution for abstracting a function from an async waterfall (async library) function (to keep my code DRY), but I kept getting an error saying that cb was not defined. Also, when passing just this to bind, the function that async was defined in was the scope, and when passing cb as well.
Eventually, I found a solution which works (adding cb as a function to the this object), but it looks a bit messy and there is probably a better way of doing it. Any suggestions?:
// registerController.js
const async = require('async');
const registerService = require('../services/register');
// Api endpoint code
...
// is there a better way than (this.cb = cb)
let callbackHandler = (err, res) => {
if (err) this.cb(err);
this.cb(null, res);
};
// asynchronously perform registration
async.waterfall([
(cb) => {
registerService.createAccount(username, email, callbackHandler.bind(this.cb = cb));
},
(res, cb) => {
registerService.doSomethingElse(domain, callbackHandler.bind(this.cb = cb);
},
....
// registerService.js
module.exports = {
createAccount: (username, email, callback) => {
httpService.request({
host: myHost,
method: 'POST',
path: '/api/signup',
body: {
username,
email
}
}).then((res) => {
return callback(null, res);
}).catch((err) => {
return callback(err);
});
},
...
}
Note: code refactored into Services file for unit testing purposes and lean controllers (taking an MVC approach)

You're not supposed to need any refactoring, async is already abstract. Just drop the callbackHandler and pass the cb directly into your function:
async.waterfall([
(cb) => {
myService.createAccount(fullName, email, cb);
},
(res, cb) => {
myService.doSomethingElse(domain, cb);
},
…
], …);
However, you get much simpler code if you just abandon async.js here and embrace promises:
// registerController.js
const registerService = require('../services/register');
// Api endpoint code
…
registerService.createAccount(username, email)
.then(res =>
registerService.doSomethingElse(domain) // supposed to return a promise as well
)
…
// registerService.js
exports.createAccount = (username, email) => { // no callback
return httpService.request({
// ^^^^^^ returns the promise
host: myHost,
method: 'POST',
path: '/api/signup',
body: {
username,
email
}
});
};
…

Related

How to wrap function call inside of Promise

After exporting the required modules and setting up the variables
let AWS = require("aws-sdk");
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
const USER_POOL_ID = 'us-east-1_vkXRQuP4U';
const CLIENT_ID = 'mipa4trls0l7323om33mlk80e8';
const poolData = {
UserPoolId : USER_POOL_ID, ClientId : CLIENT_ID
};
const POOL = new AmazonCognitoIdentity.CognitoUserPool(poolData);
let email = "my.email#domain.com";
let password = "My.Password!";
I can go ahead and call signUp command:
POOL.signUp(email, password, [], null, function(err, result) {
console.log('...result:', result);
});
And it works well. Next I want to wrap the POOL.signUp(email, password...) inside async function sign_up like so:
async function sign_up(email, password) {
POOL.signUp(email, password, [], null, function(err, result) {
console.log('...sign_up.result:', result);
return result;
})
};
async function main() {
let signupData = await sign_up(email, password);
console.log('...main.signupData:', signupData);
return signupData;
};
main().then((error, data) => {console.log('...error, data:', error, data)});
While it works fine, the order of the calls that get executed is wrong as the main function doesn't wait for the sign_up() function to complete. In attempt to correct this behavior I wrap the POOL.signUp(email, password...) inside of Promise:
async function sign_up(email, password) {
return await new Promise((resolve) => {
POOL.signUp(email, password, [], null, {
onSuccess: (result) => {
console.log('...result:', result)
return resolve(result);
},
onFailure: (err) => {
return resolve(err.message);
},
});
})
};
But I am getting the error message:
UnhandledPromiseRejectionWarning: TypeError: callback is not a function
Is there a way to avoid this error?
don't need to await the Promise you are returning (since we precisely want our function to be async, we want to defer the waiting to the caller)
Promise constructor function needs to provide the second reject parameter to be able to access the callback in the function implementation
pass a callback function as your POOL.signUp fourth argument, instead of an object
function sign_up(email, password) {
return new Promise((resolve, reject) => {
POOL.signUp(email, password, [], null, function(err, result) {
if (err) {
return reject(err.message);
}
console.log('...result:', result)
resolve(result);
});
})
};

Jest mocking implementation of a method accepting callback of third party

I am using Nest + Cognito to authenticate a user on an application, I have a method inside my Authentication service that I am trying to test/mock which is:
async cognitoRegister(userPool: CognitoUserPool, {
name,
password,
email
}: AuthRegisterInput): Promise < ISignUpResult > {
return new Promise((resolve, reject) => {
return userPool.signUp(
name,
password,
[new CognitoUserAttribute({
Name: 'email',
Value: email
})],
null,
(err, result) => {
if (!result) {
reject(err);
} else {
resolve(result);
}
},
);
});
}
Here the signUp function is a method coming from the third party CognitoUserPool which I managed to mock using module name mapper inside my package.json, here it is:
function CognitoUserPool(data) {
const { UserPoolId, ClientId } = data;
this.userPoolId = UserPoolId;
this.clientId = ClientId;
this.getCurrentUser = jest.fn().mockReturnValue("cognitouserpool");
// This method
this.signUp = jest.fn().mockReturnValue(true);
}
module.exports = CognitoUserPool;
and is implementation:
module.exports = {
CognitoUserPool: jest.fn().mockImplementation(require("./CognitoUserPool")),
};
Since the signUp method accept a callback which is responsible of giving me a result/reject value I should somehow mock it otherwise Jest is giving me a timeout error since the implementation returns a Promise that stays in a pending state.
Basically I am trying to mock a function of this kind:
const foo = (arg1, cb) => {
...do something...
}
const bar = (arg1, arg2...) => {
return new Promise((resolve, reject) => {
return foo(arg1, (err, result) => {
if (!result) {
reject(err)
} else {
resolve(result)
}
})
})
}
Here is what I am trying to do inside my test:
it("should register a cognito user", async () => {
const mockedCongitoUserPool = new CognitoUserPool({
UserPoolId: authConfig.userPoolId,
ClientId: authConfig.clientId,
});
const result = await service.cognitoRegister(mockedCongitoUserPool, {
...mockedUser,
});
console.log(result);
});
I also have a git for that can be helpful:
Main service link
Mocked third party implementation link
Tests implementation link
Any help here is appreciated <3, ask for any further explanation I really need some help on this one.
To have a static resolution, your mocked module should declare an implementation like the following instead of just a return value:
this.signUp = jest.fn().mockImplementation((name, pwd, attlist, something, cb) => {
process.nextTick(cb(null, 'signedup!'))
});
Here process.nextTick just simulates async, but you could just call cb(null, 'some result') as well if you don't care.
If you want to dynamically control the callback resolution, you could override the default mocked implementation depending on your scenario:
let cup: CognitoUserPool;
beforeAll(() => { // or maybe beforeEach, depends on what the mock keeps/does
cup = new CognitoUserPool({ userPoolId: 'fakeUserPoolId', ClientId: 'fakeClientId' });
});
it('should resolve', async () => {
const expected = { any: 'thing' };
cup.signUp.mockImplementation((a, b, c, d, cb) => cb(null, expected));
await expect(service.cognitoRegister(cup, mockedUser)).resolves.toBe(expected);
});
it('should fail', async () => {
const expected = new Error('boom!');
cup.signUp.mockImplementation((a, b, c, d, cb) => cb(expected));
await expect(service.cognitoRegister(cup, mockedUser)).rejects.toThrow(expected);
});

Combine two callbacks into one return

So I have this code:
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
lalamove.getQuotation(data ,context, function(err, llm_data){
callback(null,llm_data)
});
};
So it calls lalamove.getQuotation function and returns an object:
{ "totalFee": "108", "totalFeeCurrency": "PHP" }
Now, I have added a new function, that returns this object:
{ "totalFee": "10", "totalFeeCurrency": "PHP" }
from a different function so I thought I should push them in one array and then that is when I would call the callback but it does not work, this is what I have tried
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
var response = []
lalamove.getQuotation(data ,context, function(err, llm_data){
const llm_obj = { "lalamove": llm_data }
response.push(llm_obj);
});
inhouse.getQuotation(data ,context, function(err, ih_data){
const ih_obj = {"inhouse": ih_data }
response.push(ih_obj);
});
callback(null,response);
};
and what I want to be the response is like this:
["lalamove": { "totalFee": "108", "totalFeeCurrency": "PHP" },
"inhouse": { "totalFee": "10", "totalFeeCurrency": "PHP" }]
what am I doing wrong?
Your callback(null,response) will not wait for those two callback functions to finish. You can use Promise and use Promise.all(objs).then(function) to wait for all promises finish and run.
Welcome to World's Javascript world - Callback hell.
We have some options for your case: Callback hell, async lib, Promise, async/await...
Callback hell: Call a async function in a callback
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
var response = []
lalamove.getQuotation(data, context, function (err, llm_data) {
const llm_obj = { "lalamove": llm_data }
response.push(llm_obj);
// lalamove.getQuotation done!
// call next action
inhouse.getQuotation(data, context, function (err, ih_data) {
const ih_obj = { "inhouse": ih_data }
response.push(ih_obj);
// inhouse.getQuotation done!
// call the last action
callback(null, response);
});
});
};
Async lib: async
You can use waterfall function to do actions in order, and parallel if order is not matter.
module.exports.getEstimate = (event, context, callback) => {
var data = JSON.parse(event.body);
var response = []
async.parallel([
function (next) {
lalamove.getQuotation(data, context, function (err, llm_data) {
// TODO: check err object
const llm_obj = { "lalamove": llm_data }
response.push(llm_obj);
// lalamove.getQuotation done!
// do next action
next();
});
},
function (next) {
inhouse.getQuotation(data, context, function (err, ih_data) {
const ih_obj = { "inhouse": ih_data }
response.push(ih_obj);
// inhouse.getQuotation done!
// do next action
next()
});
}
], function (err) {
// TODO: check err object
// call the last action
callback(null, response);
});
};
Try wrapping two quotation calls in Promise, then utilise Promise.all to wait for both of them to be completed, then return the result to the callback
module.exports.getEstimate = (event, context, callback) => {
let data = JSON.parse(event.body);
// wrap quotation calls in `Promise`
Promise.all([
new Promise(resolve => lalamove.getQuotation(data, context, (err, lalamove) => resolve({ lalamove }))),
new Promise(resolve => inhouse.getQuotation (data, context, (err, inhouse ) => resolve({ inhouse }))),
]).then(response => {
// return the result back to `callback`
callback(null, response);
})
};
You could also try using util.promisify and the async / await syntax.
For example:
const util = require("util");
module.exports.getEstimate = async (event, context, callback) => {
let data = JSON.parse(event.body);
try {
let response = await Promise.all([ util.promisify(lalamove.getQuotation)(data, context),
util.promisify(inhouse.getQuotation)(data, context) ]);
callback(null, response);
} catch (err) {
callback(err);
}
};
We can also do something similar, but without async / await:
const util = require("util");
const getEstimate = (event, context, callback) => {
let data = JSON.parse(event.body);
Promise.all([util.promisify(lalamove.getQuotation)(data, context),
util.promisify(inhouse.getQuotation)(data, context)])
.then(response => callback(null, response))
.catch(err => callback(err));
};

JavaScript: Converting RSS into JSON in AWS Lambda Node app

I'm currently working on writing a function on AWS Lambda. I want to convert a RSS feed into a JSON and give that as a response in the body when making to the Lambda endpoint.
I'm using an npm package to convert the RSS to JSON. However, when I run the code. I see that I get undefined in the conversion of the RSS URL. Here is the following code:
const feed = require('rss-to-json');
exports.handler = async (event) => {
let rssFeed = event.queryStringParameters.rssFeed;
let rssAsJsonData = convertRssIntoJson(rssFeed);
return sendRes(200, rssAsJsonData);
};
const sendRes = (status, body) => {
var response = {
isBase64Encoded: true|false,
statusCode: status,
headers: {
"Content-Type": "application/json"
},
body: body,
};
return response;
};
function convertRssIntoJson (rssFeed) {
console.log(rssFeed);
return feed.load(rssFeed, function(err, rss){
if(err) {
console.log("Error: ${err}");
return;
}
console.log(rss)
return rss;
});
};
However, in the logs I get undefined when console.log(rssAsJsonData).
However, when debugging I was able to see console.log(rss) working when I change body to be body: json.stringify("TESTING")
However, it only worked when logging to the console not when I tried to pass it to the body body: body, I can't seem to find what the error is. I'm moving from Ruby to JavaScript for this project maybe I'm missing something.
I'm using Postman to make the calls:
function convertRssIntoJson (rssFeed) {
console.log(rssFeed);
return feed.load(rssFeed, function(err, rss){
if(err) {
console.log("Error: ${err}");
return;
}
console.log(rss)
return rss;
});
};
The piece of code above is a callback. Under the hood, feed.load is asynchronous, which makes your callback be executed asynchronously.
Now, when you invoke your function like this
let rssAsJsonData = convertRssIntoJson(rssFeed);
your rss object inside convertRssIntoJson does not hold any value yet, because the callback hasn't been populated up to now. This is where your undefined comes from.
Callbacks themselves don't make code asynchronous by default, but NodeJS works with a non-blocking IO model and, since feed.load is an IO call, it will be executed asynchronously.
You have a few options now, but I will list only two. A not-so-nice and a nice solution:
1) The not-so-nice way to fix it is to add a callback as argument to your convertRssIntoJson function and pass the value of that rss object upstream. The not-so-nice full code can be found below:
const feed = require('rss-to-json');
exports.handler = async (event) => {
let rssFeed = event.queryStringParameters.rssFeed;
convertRssIntoJson(rssFeed, (err, data) => {
if (err) {
return sendRes(500, { message: 'There was an err: ' + err.message })
}
return sendRes(200, data)
})
};
const sendRes = (status, body) => {
var response = {
isBase64Encoded: true | false,
statusCode: status,
headers: {
"Content-Type": "application/json"
},
body: body,
};
return response;
};
const convertRssIntoJson = (rssFeed, callback) => {
console.log(rssFeed);
feed.load(rssFeed, function (err, rss) {
if (err) {
console.log("Error: ${err}");
callback(err, undefined)
}
console.log(rss)
callback(undefined, rss)
});
};
2) The nice, clean, elegant and recommended solution is this one. Wrap your callback in a Promise, like this
function convertRssIntoJson(rssFeed) {
console.log(rssFeed);
return new Promise((res, rej) => {
feed.load(rssFeed, function (err, rss) {
if (err) {
console.log("Error: ${err}");
return rej(err)
}
console.log(rss)
return res(rss)
});
})
};
Since your handler is async, it means it can just await on Promises.
So your client code is now as simple as:
return sendRes(200, await convertRssIntoJson(rssFeed));
Your final code will look like (I have refactored a little bit to make use of arrow functions):
const feed = require('rss-to-json');
exports.handler = async (event) => {
let rssFeed = event.queryStringParameters.rssFeed;
return sendRes(200, await convertRssIntoJson(rssFeed));
};
const sendRes = (status, body) => {
var response = {
isBase64Encoded: true | false,
statusCode: status,
headers: {
"Content-Type": "application/json"
},
body: body,
};
return response;
};
const convertRssIntoJson = (rssFeed) => {
console.log(rssFeed);
return new Promise((res, rej) => {
feed.load(rssFeed, (err, rss) => {
if (err) {
console.log("Error: ${err}");
return rej(err)
}
console.log(rss)
return res(rss)
});
})
};
If you want to know more about async/await, you can see it in here.
EDIT: Code refactor and code added for solution 1)

Properly chaining functions in Firebase function

I am building a function in Firebase Cloud Functions, which can utilize Node.js modules.
I am still new to the use of .then() and I'm struggling to figure out a way to chain my 3 functions webhookSend(), emailSendgrid(), and removeSubmissionProcessor() that happen right after the 'count' is incremented (the if statement that checks temp_shouldSendWebhook). The whole idea of returning promises still confuses me a little, especially when it it involves external libraries.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const request = require('request');
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);
const SENDGRID_API_KEY = firebaseConfig.sendgrid.key;
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
exports.submissionProcess = functions.database.ref('/submissions/processor/{submissionId}').onWrite((change, context) => {
var temp_metaSubmissionCount = 0; // omitted part of function correctly sets the count
var temp_shouldSendWebhook = true; // omitted part of function correctly sets the boolean
return admin.database().ref('/submissions/saved/'+'testuser'+'/'+'meta').child('count')
.set(temp_metaSubmissionCount + 1)
.then(() => {
// here is where im stuck
if (temp_shouldSendWebhook) {
webhookSend();
emailSendgrid();
removeSubmissionProcessor();
} else {
emailSendgrid();
removeSubmissionProcessor();
}
})
.catch(() => {
console.error("Error updating count")
});
});
function emailSendgrid() {
const user = 'test#example.com'
const name = 'Test name'
const msg = {
to: user,
from: 'hello#angularfirebase.com',
subject: 'New Follower',
// text: `Hey ${toName}. You have a new follower!!! `,
// html: `<strong>Hey ${toName}. You have a new follower!!!</strong>`,
// custom templates
templateId: 'your-template-id-1234',
substitutionWrappers: ['{{', '}}'],
substitutions: {
name: name
// and other custom properties here
}
};
return sgMail.send(msg)
}
function webhookSend() {
request.post(
{
url: 'URLHERE',
form: {test: "value"}
},
function (err, httpResponse, body) {
console.log('REQUEST RESPONSE', err, body);
}
);
}
function removeSubmissionProcessor() {
admin.database().ref('/submissions/processor').child('submissionkey').remove();
}
I want to be able to construct the 3 functions to be called one after another such that they will all execute.
In order to chain these functions, they each need to return a promise. When they do, you can call them sequentially like this:
return webhookSend()
.then(() => {
return emailSendgrid();
})
.then(() => {
return removeSubmissionProcessor();
});
Or in parallel like this:
return Promise.all([webhookSend, emailSendgrid, removeSubmissionProcessor]);
Now, to make your functions return promises:
emailSendgrid: It looks like this returns a promise (assuming sgMail.send(msg) returns a promise), so you shouldn't need to change this.
removeSubmissionProcessor: This calls a function that returns a promise, but doesn't return that promise. In other words it fires off an async call (admin.database....remove()) but doesn't wait for the response. If you add return before that call, this should work.
webhookSend calls a function that takes a callback, so you'll either need to use fetch (which is promise-based) instead of request, or you'll need to convert it to return a promise in order to chain it:
function webhookSend() {
return new Promise((resolve, reject) => {
request.post(
{
url: 'URLHERE',
form: {test: "value"}
},
function (err, httpResponse, body) {
console.log('REQUEST RESPONSE', err, body);
if (err) {
reject(err);
} else {
resolve(body);
}
}
);
});
}
Use async functions and then you can use .then() or await before every function calls
for reference read this

Categories