Wait for inner functions to wait and execute then proceed execution - javascript

I am executing a cloud function which is written in nodeJS.Here the function triggers when a data from the external source comes in and in this function, I have to call and check DB at the particular table but it takes more than 5 seconds and before the execution of my getDataFromDb function my main function completed execution. Plus there is a function called updateItems(postdate); and it executes if I cannot find data in my DB when triggering getDataFromDb
I tried async await but I am not sure where I am doing wrong. my function always ends first before my DB operation ends.
exports.handler = (event, context) => {
//here i am making data ready for DB and checking for the record if that is present in db
getDataFromDb(uniqueArray);
function getDataFromDb(uniqueArray) {
var params = {
// params for get reques
}
//db get operation
db.get(params, function (err, data) {
//takes time here
if (err) {
console.log(err); // an error occurred
}
else {
//another DB operation updateItems(postdata);
//takes time here
}
else {
console.log("first run for db")
//another DB operation updateItems(postdata);
//takes time here
}
}
});
}
});
console.log("main function ended")
};
the expected result should wait for the inner function to execute then end the main function but actually, the main function ends first then DB calling function ends

Though this can be achieved through callbacks, converting it to promise chain makes it easy, as execution of inner function depends on outer function, it's better to chain promises i.e. return Promise in the call back of first function, to execute them serially.
exports.handler = (event, context) => {
getDataFromDb(uniqueArray).then(success => {
console.log('Done')
})
.catch(err => {
console.log('handle get or post err here');
});
function getDataFromDb(uniqueArray) {
var params = {};
return new Promise((resolve, reject) => {
db.get(params, (err, data) => {
if (err) {
return reject(err); // an error occurred
} else {
return resolve(data);
}
});
}).then(success => updateItems(data))
}
});

Related

I can't store response comes from payment api

ı have been devoloing an e-commerce system but ı have problem with iyzipay payment api. I make successful request and get response from server but I can't store data comes from server. anyone help?
let returnedData = {}
iyzipay.payment.create(paymentRequest, function (err, result) {
if (err) {
return next(new CustomError("Ödeme başarısız.", 500))
}
return returnedData = result
})
//ı can't see data here and return empty {}
console.log(returnedData)
// but ı can see here when ı wait 1 second
setTimeout(() => {
console.log(returnedData)
}, 1000);
Since you're calling an async function you should await for it's result. That's why your first console log doesn't print the result and ater 1 sec does, because in that time(+ the execution time for the stack) you're getting the result
The function that can be awaited:
function createPayment(paymentRequest) {
return new Promise((resolve, reject) => {
iyzipay.payment.create(paymentRequest, function (err, result) {
if (err) reject(new CustomError("Ödeme başarısız.", 500));
resolve(result);
});
});
}
Generally:
(async () => {
const request = {/* whatever */};
try {
const res = await createPayment(request);
console.log(res); // Now it's here
} catch(e) { console.error(e); }
})();
You're probably in a route so the actual code would be:
app.use('/payment', async (req, res, next) => {
try {
const result = await createPayment(req);
console.log(result); // Now it's here
res.end();
} catch(e) { next(e); }
})
Your first console is getting called before the request gets complete. When waited for 1 sec request is completed and response is stored in the returnData variable.
So for your requirement try using promise or call a function to store the response from the create payment function itself with the response as a parameter.

Node.js promise .then() not sequence

it's run .then() number2 before number1 done.
P.S. getConnectIn() is promise
function query(sql){
var data = [555,555];
getConnectIn()
.then((check_connect)=>{ //then 1
if(check_connect){
connector.query(sql,function(err,result,fields){
data = result;
});
setTimeout(()=>{console.log("data before>",data);},1000);
}
})
.then(()=>{ //then 2
console.log("data after>",data);
})
.catch((err)=>{console.log("error >",err)})
}
display picture
You are using then in wrong way. In first then handler method you are not returning anything which is why JS engine will continue running next then in the chain. Update your code to:
function query(sql) {
var data = [555, 555];
getConnectIn()
.then((check_connect) => { //then 1
if (check_connect) {
return new Promise((resolve, reject) => {
connector.query(sql, function(err, result, fields) {
If (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
})
.then(result => { //then 2
// now you can access result here!
console.log("data after>", data);
})
.catch((err) => {
console.log("error >", err)
})
}
Take a look at MDN page to learn more about promise chaining.
Basically 1 then call first but you call async method again (connector.query) which takes time and control pass to next call and then setTimeout pass execution process to next , either return new promise when call setTimeout or Use second then after your async connector.query() method like this. here you don't need timeout also
function query(sql){
var data = [555,555];
getConnectIn()
.then((check_connect)=>{ //then 1
if(check_connect){
connector.query(sql,function(err,result,fields){
// data = result;
}).then((rslt)=>{ //then 2
console.log("data after>",rslt);
})
.catch((err)=>{console.log("error >",err)}
}
});
}

NodeJS + ExpressJS: How to wait for forEach to finish with SQL queries inside

I'm trying to wait for a forEach to finish, and the forEach loop has two nested requests inside.
I need to wait untill the forEach finish beacuse I fill an array with the queries results and then, when the forEach is finish, then call another function, but I cannot do it well because sometimes, the array is fully filled, but othertimes the array is incomplete.
Here is my code:
readAllClientsAndInvoices: function(request, response) {
let clientsInvoices = [];
DAOClients.readAllClientesById(request.session.id, function (err, clients) {
if (err) {
console.log(err);
} else {
clients.forEach(function (client, idx, array) {
DAOClients.readClientDataById(client.id, function (err, data) {
if (err) {
console.log(err)
} else {
DAOClients.readAllclientInvoices(data.id, function (err, invoices) {
if (err) {
console.log(err);
} else {
let pair = {
clientData: data,
invoicesList: invoices
};
clientsInvoices.push(pair);
}
});
}
if (idx === array.length - 1) {
DAOClients.createClientPDFReportWOCommentsV2(clientsInvoices, function (err, res) {
if (err) {
console.log(err);
} else {
response.redirect(307, '/client/searchClient');
}
});
}
});
});
}
});
}
This is how I do it now, but I need to wait untill the array is fully filled with all the clients and its invoices and then call to createclientPDFReportWOCommentsV2 function but I don't know how to do it.
Thanks everyone
You can try to use a map instead of forEach in order to accept a return value from every call of the callback function, that return value will have to be a Promise, resolving after particular call has been completed. Since I don't see any particular error handling in your example I just made it so that in case of error Promise resolves undefined which is filtered afterwards in the createClientPDFReportWOCommentsV2 call.
function readAllClientsAndInvoices(request, response) {
DAOClients.readAllClientesById(request.session.id, function (err, clients) {
if (err) return console.log(err);
Promise.all(clients.map(client => {
return new Promise(resolve => {
DAOClients.readClientDataById(client.id, function (err, data) {
if (err) {
console.log(err)
resolve();
} else {
DAOClients.readAllclientInvoices(data.id, function (err, invoices) {
if (err) {
console.log(err);
resolve();
} else {
let pair = {
clientData: data,
invoicesList: invoices
};
resolve(pair);
}
});
}
});
});
})).then(clientsInvoices => {
DAOClients.createClientPDFReportWOCommentsV2(clientsInvoices.filter(Boolean), function (err, res) {
if (err) {
console.log(err);
} else {
response.redirect(307, '/client/searchClient');
}
});
});
});
}
To solve these problems i would use Async/Await https://javascript.info/async-await. Make sure all the methods you're calling on DAOClients returns a Promise https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
For example
function readAllClientesById() {
return new Promise((resolve, reject) => {
// Wait for some data to get fetched from SQL
// and call resolve instead of callback function
resolve(data)
// Or of there was an error
reject(err)
})
}
This is natively supported in the latest versions of Node.js.
Example of Async/Await if promises is implemented:
async function readAllClientsAndInvoices(req, res) {
try {
const clientInvoices = []
const clients = await DAOClients.readAllClientesById(req.session.id)
for (const client of clients) {
const clientData = await DAOClients.readClientDataById(client.id)
const clientInvoices = await DAOClients.readAllclientInvoices(clientData.id)
clientInvoices.push({
clientData: data,
invoicesList: invoices
})
}
// This code won't be executed until the for loop is completed
await DAOClients.createClientPDFReportWOCommentsV2(clientInvoices)
} catch (err) {
return res.status(err.code).send(err)
}
res.redirect(307, '/client/searchClient');
}
I haven't tested the code, it's just an example of how I approach these type of problems.

How to handle an unexpected error from a function after result is return from function in JavaScript?

While working on asynchronous functions in JavaScript. I discovered this problem.
A function needs to perform some asynchronous task, however that task doesn't make difference to function result. So function return its result, and after a while an exception is thrown from asynchronous function, however since control is already returned so exception goes unhanded.
Note: Not to change notify I'm intentionally throwing error like that, need to only handle this exception
Code is like:
function notify() { //To send notification
setTimeout( function() { //Just to simulate an asynchronous function
throw "Exception occurred"; //An exception from async function
},2000);
return "Notified";
}
try {
let result = notify();
console.log(result); //Notified
}
catch (error) { //Never comes to catch block.
console.log(error);
}
How can to catch this exception.
Tried using Promise, however it doesn't resolved with promise because even with promise, flow or function is removed from memory as calling function have received response.
Code using Promises
function notify() { //To send notification
return new Promise ( (resolve, reject) => {
setTimeout( function() { //Just to simulate an asynchronous function
throw "Exception occurred"; //An exception from async function
},2000);
resolve("Notified");
});
}
notify()
.then( result => {
console.log(result); //Notified
})
.catch( error => { //Never comes to catch block
console.log(error);
});
How can I catch exception in JavaScript programming?
I would wrap setTimeout in a promise as:
const delay = ms => new Promise(res => setTimeout(res, ms));
That allows you to write notify as:
async function notify() {
await delay(2000);
throw new Error();
}
notify().then(/*...*/).catch(/*...*/);
The trick here is that throw is inside of an asynchronous function and not as in your case inside of a callback.
var notify = function () {
anOddTask();
return "Notified";
}
async function anOddTask() {
try {
await setTimeout( function() {
},2000);
throw new Error("Exception occurred");
}
catch (err) {
console.log(err);
}
}
try {
let result = notify();
console.log(result); //Notified
}
catch (error) { //Never comes to catch block.
console.log(error);
}
This will immediately return "Notify" and handle the rejection at some later time. If you wish that "Notify" be returned as a resolved promise than just make notify() an async function.
var notify = async function () {
anOddTask();
return "Notified";
}

Run callback function after forEach is done

In the project, I have a loop going through a list of urls. It downloads file from every url and do some post process over the downloaded file.
After the all the process done (both download process and post process), I want to execute a callback function. Because post process includes some streaming task, it has close event. If the last item can be identified, I can pass the callback function to the close event. However, since the loop is async, I can't track which item is done at last.
For now, I use a 5 second timeout to make sure the callback is executed after the whole process. Obviously, this is not sustainable. What's a good way to handle this?
loop code:
exports.processArray = (items, process, callback) => {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
// execute download and post process each second
// however it doesn't guarantee one start after previous one done
setTimeout(arguments.callee, 1000);
} else {
setTimeout(() => {callback();}, 5000);
}
}, 1000);
};
processArray(
// First param, the array
urlList,
// Second param, download and post process
(url) => {
if(url.startsWith('http')) {
getDataReg(url, uid);
}
else if(url.startsWith('ftp')) {
getDataFtp(url, uid);
}
else {
console.log('not a valid resource');
}
},
// Third param, callback to be executed after all done
() => {
Request.get(`${config.demouri}bound=${request.query.boundary};uid=${uid}`, {
method: 'GET',
auth: auth
})
.on('response', (response) => {
console.log('response event emmits');
zipFiles(uid)
.then((path) => {
reply.file(path, { confine: false, filename: uid + '.zip', mode: 'inline'}).header('Content-Disposition');
});
});
}
);
Download and post process:
exports.getDataFtp = (url, uid) => {
console.log('get into ftp');
var usefulUrl = url.split('//')[1];
var spliter = usefulUrl.indexOf('/');
var host = usefulUrl.substring(0, spliter);
var dir = usefulUrl.substring(spliter+1, usefulUrl.length);
var client = new ftp();
var connection = {
host: host
};
var fileNameStart = dir.lastIndexOf('/') + 1;
var fileNameEnd = dir.length;
var fileName = dir.substring(fileNameStart, fileNameEnd);
console.log('filename: ', fileName);
client.on('ready', () => {
console.log('get into ftp ready');
client.get(dir, (err, stream) => {
if (err) {
console.log('get file err:', err);
return;
} else{
console.log('get into ftp get');
stream.pipe(fs.createWriteStream(datadir + `download/${uid}/${fileName}`));
stream.on('end', () => {
console.log('get into ftp close');
unzipData(datadir + `download/${uid}/`, fileName, uid);
client.end();
});
}
});
});
client.connect(connection);
};
exports.getDataReg = (url, uid) => {
console.log('get into http');
var fileNameStart = url.lastIndexOf('/') + 1;
var fileNameEnd = url.length;
var fileName = url.substring(fileNameStart, fileNameEnd);
var file = fs.createWriteStream(datadir + `download/${uid}/${fileName}`);
if (url.startsWith('https')) {
https.get(url, (response) => {
console.log('start piping file');
response.pipe(file);
file.on('finish', () => {
console.log('get into http finish');
unzipData(datadir + `download/${uid}/`, fileName, uid);
});
}).on('error', (err) => { // Handle errors
fs.unlink(datadir + `download/${uid}/${fileName}`);
console.log('download file err: ', err);
});
} else {
http.get(url, (response) => {
console.log('start piping file');
response.pipe(file);
file.on('finish', () => {
unzipData(datadir + `download/${uid}/`, fileName, uid);
});
}).on('error', (err) => {
fs.unlink(datadir + `download/${uid}/${fileName}`);
console.log('download file err: ', err);
});
}
};
function unzipData(path, fileName, uid) {
console.log('get into unzip');
console.log('creating: ', path + fileName);
fs.createReadStream(path + fileName)
.pipe(unzip.Extract({path: path}))
.on('close', () => {
console.log('get into unzip close');
var filelist = listFile(path);
filelist.forEach((filePath) => {
if (!filePath.endsWith('.zip')) {
var components = filePath.split('/');
var component = components[components.length-1];
mv(filePath, datadir + `processing/${uid}/${component}`, (err) => {
if(err) {
console.log('move file err: ');
} else {
console.log('move file done');
}
});
}
});
fs.unlink(path + fileName, (err) => {});
});
}
After the all the process done (both download process and post process), I want to execute a callback function.
The interesting thing about a series of asynchronous processes is that you can never know when exactly all processes will complete. So setting a timeout for the callback is quick&dirty way to do it, but it's not reliable for sure.
You can instead use a counter to solve this problem.
Let's say you have 10 operations to perform. At the beginning you set your counter to ten counter = 10 And after each process is completed, regardless how (it can either succeed or fail), you can decrement the counter by 1 like counter -= 1 and right after it you can check if the counter is 0, if so that means all processes are completed and we reached the end. You can now safely run your callback function, like if(counter === 0) callback();
If I were you, I would do something like this:
*Notice that the called process should return a promise, so that I can know when it finishes (again regardless how)
*If you need help about promises, this useful article might help you: https://howtonode.org/promises
*Oh and one more thing, you should avoid using arguments.callee, because it's deprecated. Here is why Why was the arguments.callee.caller property deprecated in JavaScript?
exports.processArray = (items, process, callback) => {
var todo = [].concat(items);
var counter = todo.length;
runProcess();
function runProcess() {
// Check if the counter already reached 0
if(checkCounter() === false) {
// Nope. Counter is still > 0, which means we got work to do.
var processPromise = process(todo.shift());
processPromise
.then(function() {
// success
})
.catch(function() {
// failure
})
.finally(function() {
// The previous process is done.
// Now we can go with the next one.
--counter;
runProcess();
})
}
};
function checkCounter() {
if(counter === 0) {
callback();
return true;
} else {
return false;
}
}
};
What you want to do is to make all your asynchronous processes converge into a single promise that you can use to execute the callback at the correct moment.
Lets start at the point each process is complete, which I assume is in the callback passed to the mv() function in unzipData(). You want to wrap each of these asynchronous actions in a Promise that resolves in the callback and you also want to use these promises later and for that you use the .map() method to collect the promises in an array (instead of .forEach()).
Here's the code:
var promises = filelist.map((filePath) => {
if (!filePath.endsWith('.zip')) {
var components = filePath.split('/');
var component = components[components.length-1];
return new Promise((resolve, reject) =>
mv(filePath, datadir + `processing/${uid}/${component}`, (err) => {
if(err) {
console.log('move file err: ');
reject(); // Or resolve() if you want to ignore the error and not cause it to prevent the callback from executing later
} else {
console.log('move file done');
resolve();
}
}));
}
return Promise.resolve();
});
(if the asynchronous action is not to be executed, a Promise that resolves immediately is returned instead)
Now, we can turn this list of Promises into a single Promise that resolves when all of the promises in the list has resolved:
var allPromise = Promise.all(promises);
Next, we need to look further up in the code. We can see that the code we've just been looking at is itself part of an event handler of an asynchronous action, i.e. fs.createReadStream(). You need to wrap that in a promise that gets resolved when the inner promises resolve and this is the promise that the unzipData() function shall return:
function unzipData(path, fileName, uid) {
console.log('get into unzip');
console.log('creating: ', path + fileName);
return new Promise((outerResolve) =>
fs.createReadStream(path + fileName)
.pipe(unzip.Extract({path: path}))
.on('close', () => {
console.log('get into unzip close');
var filelist = listFile(path);
// Code from previous examples
allPromise.then(outerResolve);
}));
}
Next, we look at the functions that use unzipData(): getDataReg() and getDataFtp(). They only perform one asynchronous action so all you need to do is to make them return a promise that resolves when the promise returned by unzipData() resolves.
Simplified example:
exports.getDataReg = (url, uid) => {
return new Promise((resolve, reject) => {
// ...
https.get(url, (response) => {
response.pipe(file);
file.on('finish', () => {
unzipData(datadir + `download/${uid}/`, fileName, uid)
.then(resolve);
});
}).on('error', (err) => { // Handle errors
fs.unlink(datadir + `download/${uid}/${fileName}`);
reject(); // Or resolve() if you want to ignore the error and not cause it to prevent the callback from executing later
});
// ...
});
}
Finally, we get to the processArray() function and here you need to do the same thing we did to begin with: map the processes into a list of promises. First, the process function passed needs to return the promises returned by getDataReg() and getDataFtp():
// Second param, download and post process
(url) => {
if(url.startsWith('http')) {
return getDataReg(url, uid);
}
else if(url.startsWith('ftp')) {
return getDataFtp(url, uid);
}
else {
console.log('not a valid resource');
}
return Promise.reject(); // or Promise.resolve() if you want invalid resources to be ignored and not prevent the callback from executing later
}
Now, your processArray() function can look like this:
exports.processArray = (items, process, callback) =>
Promise.all(items.map(process))
.then(callback)
.catch(() => console.log('Something went wrong somewhere'));
Your callback will get invoked when all asynchronous actions have completed, regardless of in which order they do. If any one of the promises rejects, the callback will never be executed so manage your promise rejections accordingly.
Here's a JSFiddle with the complete code: https://jsfiddle.net/upn4yqsw/
In general, since nodejs does not appear to have implemented Streams Standard to be Promise based, at least from what can gather; but rather, uses an event based or callback mechanism, you can use Promise constructor within function call, to return a fulfilled Promise object when a specific event has been dispatched
const doStuff = (...args) => new Promise((resolve, reject)) => {
/* define and do stream stuff */
doStreamStuff.on(/* "close", "end" */, => {
// do stuff
resolve(/* value */)
})
});
doStuff(/* args */)
.then(data => {})
.catch(err => {})

Categories