Running a function after async operation is complete - javascript

I'm banging my head against the wall to figure out how to push data that is being written on file asynchronously into an array. Writing the data synchronously (and checking if the item is the last on the list) takes too much time so I decided to make it run async. After doing some research, it seems that I could use a callback
I would prefer not to use an external library for doing this, since I'm pretty sure either a callback or a Promise should do the trick. Thanks!
//Iterate through list and make HTTP request to get data
dataDocument.map(function(item, index) {
request(item, function(err, res, html) {
if (err) throw err;
renderData(html, item);
});
});
//Renders data
function renderData(html, item) {
...some calculations here.
writeData(output, id, function() {
pushed(output);
});
};
//Writes the data on file
function writeData(output, id) {
fs.appendFile('./output.json', output);
//SHOULD I USE A CALLBACK HERE TO PUSH INTO AN ARRAY ONCE IT'S COMPLETE?
};
//NEED HELP HERE: Pushed the data into an array and eliminates last comma.
function pushed(data) {
var arr = [];
arr.push(data);
}

With promises it will look cleaner and leaner. Promisify all the involved functions, and use Promise.all to know when you have collected all data:
// Promisify all the involved callback-based functions:
function promiseRequest(item) {
return new Promise(function (resolve, reject) {
request(item, function (err, res, html) {
if (err) {
reject(err);
} else {
resolve(html);
}
})
})
}
//Renders data
function promiseRenderData(html, item) {
//...some calculations here.
return promiseWriteData(output, id).then(function() {
return output;
});
};
//Writes the data on file
function promiseWriteData(output, id) {
return new Promise(function (resolve, reject) {
fs.appendFile('./output.json', output, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
//Iterate through list and make HTTP request to get data
Promise.all(dataDocument.map(function(item, index) {
return promiseRequest(item).then(function(html) {
return promiseRenderData(html, item);
};
})).then(function(arr) {
// Do something with `arr` here
});

Related

Waiting for promise to resolve from parent function

I have a primary thread in my node application such as this:
function main_thread() {
console.log("Starting");
values = get_values(1);
console.log(values);
console.log("I expect to be after the values");
}
The get_values function calls the hgetall function using the node_redis package. This function provides a call back, but can be promisified:
function get_values(customer_id) {
// Uses a callback for result
new Promise(function(resolve, reject) {
redis_client.hgetall(customer_id, function (err, result) {
if (err) console.log(err)
console.log("About to resolve");
resolve(result);
});
})
.then(({result}) => {
console.log(result);
});
}
This works great for promise chaining within the function, however not so well in my main thread, as I can't wait and return the value.
Here's how I'd do it in ruby, the main language I use:
def get_values(customer_id)
return #redis_client.hgetall(customer_id)
end
How can I create a promise within a reusable function and make the main thread wait until the function returns the response from the promise?
EDIT:
It's been suggested the promise can be returned with a then chained in the main thread. However this still means any code in the main thread after after the function call executes before the then block.
EDIT 2:
After lengthy discussion with some IRL JS developer friends, it looks like trying to create a synchronous script is against the ethos of modern JS. I'm going to go back to my application design and work on making it async.
Here is a working example with async/await. I've replaced the redis with a timeout and an array for the data.
async function main_thread() {
console.log("Starting");
values = await get_values(1);
console.log(`After await: ${values}`);
console.log("I expect to be after the values");
}
async function get_values(customer_id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const result = [1, 2, 3];
console.log(`Resolving: ${result}`);
resolve(result);
}, 300);
});
}
main_thread();
Further reading:
Using Promises
Promise Constructor
Return the promise in get_values
function get_values(customer_id) {
// Uses a callback for result
return new Promise(function(resolve, reject) {
redis_client.hgetall(customer_id, function (err, result) {
if (err) console.log(err)
console.log("About to resolve");
resolve(result);
});
})
.then(({result}) => {
reject(result);
});
}
Now in your main thread, you could wait for it like:
function main_thread() {
console.log("Starting");
get_values(1).then(function(values) {
console.log(values);
}).catch(function(error) {
console.error(error);
});
}
Simple as returning the promise (chain) from your function
function get_values(customer_id) {
// Uses a callback for result
return new Promise(function(resolve, reject) {
redis_client.hgetall(customer_id, function (err, result) {
if (err) console.log(err)
console.log("About to resolve");
resolve(result);
});
})
.then(({result}) => {
console.log(result);
});
}
And then in your main async function or function
let result = await get_values(); or get_values.then(function(result){})
function main_thread() {
console.log("Starting");
values = get_values(1).then(function(values){
console.log(values);
console.log("I expect to be after the values");
});
}
async function main_thread() {
console.log("Starting");
let values = await get_values(1);
console.log(values);
console.log("I expect to be after the values");
}

File Loop Function

I am creating a function in node.js that loops through the files of a directory. It is supposed to add the file name to the returnData variable, then return the returnData. However, it keeps returning nothing. I've put a few console.log statements in the function to help me debug, but I can't figure out why it won't work.
function loopMusic (directory) {
var returnData = "";
fs.readdir (directory, function (err, files) {
if (err) {
console.log (err);
}
files.forEach (function (file, index) {
returnData += file;
console.log (returnData);
});
});
console.log (returnData);
return returnData;
}
The first console.log statement is able to print the files, but the one right before the return just prints a new line.
You can make the function return a promise:
function loopMusic (directory) {
return new Promise((resolve, reject) => {
fs.readdir (directory, function (err, files) {
if (err) {
reject(err);
return;
}
files.forEach (function (file, index) {
returnData += file;
console.log (returnData);
});
resolve(returnData);
});
}
You would use in that way:
loopMusic('...')
.then((data) => console.log(data))
.catch((err) => ...);
fs.readdir is asynchronous, meaning it does not return with the result when you call it. Instead the result is provided to the callback, which is called when the command finishes processing. It "calls-back" to the function you provided when it's done (hence the name).
If you wanted to do this synchronously you can do the following:
function loopMusic (directory) {
var returnData = "";
var files = fs.readdirSync(directory);
files.forEach (function (file, index) {
returnData += file;
console.log (returnData);
});
console.log(files);
return returnData;
}
That would return a string of mushed together file paths, as in your question.
However, blocking isn't usually a good idea and you should use the asynchronous version. I like to return a Promise in these situations. Here's an example that returns a promise filled with that string. This technically isn't necessary since the callback could just be used...but lets just pretend.
function loopMusic (directory) {
return new Promise(function(resolve, reject) {
fs.readdir (directory, function (err, files) {
if (err) {
return reject(err);
}
let returnData = "";
files.forEach (function (file, index) {
returnData += file;
});
resolve(returnData);
});
});
}
Usage:
var musicPromise = loopMusic(dir);
musicPromise.then((musicStr) => console.log(musicStr)), (err) => console.log(err));
The asynchronous nature of this makes it a bit hard to follow since things don't happen in order, but when using Promises the then() is used to handle what happens on success (or failure) when it does complete later on.
Finally, if you're using ES2017+ (the newest version of Node) you can use the async/await pattern. Keep in mind my promise example above:
async function loopMusicAsync(directory) {
try{
return await loopMusic(directory); //promise returned
}
catch(error) {
console.log(error); //promise rejected
return null;
}
}

Javascript Promise prematurely resolving

I have a function that returns a Promise, that accesses the database and pulls a few lines out, assigning them to a Javascript variable.
The issue is that my '.then' clause is being triggered even though I know the Promise hasn't resolved:
app.post("/api/hashtag", function (req, res) {
FindPopularRumours().then(function (resolveVar) {
console.log(resolveVar);
console.log();
res.send(resolveVar);
}).catch(function () {
console.log("DB Error!");
res.send("DB Error!");
});
});
And the Promise function:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find().forEach(function (doc) {
hashtags.push(doc.hashtag);
console.log(hashtags);
});
resolve(hashtags);
});
}
The result output is:
[ ]
['#test1']
['#test1', '#test2']
['#test1', '#test2', '#test3']
As you can see, the first line ('[ ]') should ONLY be executed AFTER the hashtags have been output. But for some reason my code seems to think the Promise has been resolved before it actually has.
EDIT1
As per Ankit's suggestion, I have amended my function to:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
db.collection(HASHTAGS).find({}, function (err, doc) {
if (!err) {
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
} else {
return reject(err);
}
});
});
}
This still returns the same output response as before (e.g the 'then' clause is running before the promise itself).
My POST function is still the same as before.
The db.collection.find() function is async, so you have to resolve the promise inside the callback for that, something like
function FindPopularRumours() {
return db.collection(HASHTAGS).find().toArray().then( (items) => {
return items.map( doc => doc.hashtag);
});
}
takes advantage of the Mongo toArray() method, that returns a promise directly
Please note that db.collection(HASHTAGS).find() is an asynchronous call. So, your promise is resolved before database query returns. To solve this problem, you need to re-write your database query as follows:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find({}, function(err, doc){
if(!err){
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
}else{
return reject(err);
}
});
});
}
Hope the answer helps you!

Conditional async function

I have my function getting an email from Gmail. I want to run this function n times or until an email is found.
What is a proper way to do it? I tried: http://caolan.github.io/async/docs.html#retry but without success.
I was following this article how to read emails: https://developers.google.com/gmail/api/quickstart/nodejs
Assuming you have a routine called gmail, which returns a promise which succeeds (fulfills) if an email is found, and otherwise fails (rejects), then:
function get(n) {
return gmail().catch(() => {
if (!n) throw "Too many tries!";
return get(--n);
};
}
Usage:
get(5).then(
mail => console.log(mail.body),
() => console.log("No mail!"));
If for some reason you do not like the recursive style:
function get(n) {
let promise = Promise.reject();
do { promise = promise.catch(gmail); } while (n--);
return promise;
}
If gmail is callback style, then
function get(n, cb) {
gmail(function(err, data) {
if (err)
if (!n) get(--n, cb);
else cb("Too many tries!");
else cb(null, data);
});
}
Or better yet, promisify gmail, either using a library or
function promisify(fn) {
return new Promise((resolve, reject) {
fn(function(data, err) {
if (err) reject(err);
else resolve(data);
});
});
}
and then replace gmail in the first solution with promisify(gmail).

How to properly chain Promises with nesting

My node project currently contains a sideway christmas tree of nested callbacks in order to fetch data and process them in the right order. Now I'm trying refactor that using Promises, but I'm unsure how to do it properly.
Let's say I'm fetching a list of offices, then for each office all their employees and then each employees' salary. In the end all entities (offices, employees and salaries) should be linked together and stored in a database.
Some pseudo-code illustrating my current code (error handling omitted):
fetch(officesEndpoint, function (data, response) {
parse(data, function (err, offices) {
offices.forEach(function (office) {
save(office);
fetch(employeesEndPoint, function (data, response) {
parse(data, function (err, employees) {
// link each employee to office
save(office);
save(employee);
employees.forEach(function () {
fetch(salaryEndpoint, function (data, response) {
parse(data, function (err, salaries) {
// link salary to employee
save(employee);
});
});
});
});
});
});
});
});
I tried solving this with promises, but I have a couple of problems:
kind of verbose?
each office needs to be linked to their respective employees, but in the saveEmployees function I only have access to the employees, not the office from further up in the chain:
var restClient = require('node-rest-client');
var client = new restClient.Client();
var xml2js = require('xml2js');
// some imaginary endpoints
var officesEndpoint = 'http://api/offices';
var employeesEndpoint = 'http://api/offices/employees';
var salaryEndpoint = 'http://api/employees/:id/salary';
function fetch (url) {
return new Promise(function (resolve, reject) {
client.get(url, function (data, response) {
if (response.statusCode !== 200) {
reject(statusCode);
}
resolve(data);
});
});
}
function parse (data) {
return new Promise(function (resolve, reject) {
xml2js.parseString(data, function (err, result) {
if (err) {
reject(err);
}
resolve(result);
});
});
}
function saveOffices (offices) {
var saveOffice = function (office) {
return new Promise(function (resolve, reject) {
setTimeout(function () { // simulating async save()
console.log('saved office in mongodb');
resolve(office);
}, 500);
})
}
return Promise.all(offices.map(saveOffice));
}
function saveEmployees (employees) {
var saveEmployee = function (employee) {
return new Promise(function (resolve, reject) {
setTimeout(function () { // simulating async save()
console.log('saved employee in mongodb');
resolve(office);
}, 500);
})
}
return Promise.all(offices.map(saveEmployee));
}
fetch(officesEndpoint)
.then(parse)
.then(saveOffices)
.then(function (savedOffices) {
console.log('all offices saved!', savedOffices);
return savedOffices;
})
.then(function (savedOffices) {
fetch(employeesEndPoint)
.then(parse)
.then(saveEmployees)
.then(function (savedEmployees) {
// repeat the chain for fetching salaries?
})
})
.catch(function (error) {
console.log('something went wrong:', error);
});
You don't necesseraly have to nest, this would work too:
fetch(officesEndpoint)
.then(parse)
.then(saveOffices)
.then(function(savedOffices) {
console.log('all offices saved!', savedOffices);
return savedOffices;
})
.then(function(savedOffices) {
// return a promise
return fetch(employeesEndPoint); // the returned promise can be more complex, like a Promise.all of fetchEmployeesOfThisOffice(officeId)
})
// so you can chain at this level
.then(parse)
.then(saveEmployees)
.then(function(savedEmployees) {
return fetch(salariesEndPoint);
})
.catch(function(error) {
console.log('something went wrong:', error);
});
Your promisified functions fetch, parse, saveOffices and saveEmployees are fine. With those, you can refactor your current code to use promises, chain instead of nest where applicable, and leave out a bunch of error handling boilerplate:
fetch(officesEndpoint)
.then(parse)
.then(function(offices) {
return Promise.all(offices.map(function(office) {
return save(office)
.then(function(){ return fetch(employeesEndPoint); })
.then(parse)
.then(function(employees) {
// link each employee to office
// throw in a Promise.all([save(office), save(employee)]) if needed here
return Promise.all(employees.map(function(employee) {
return fetch(salaryEndpoint)
.then(parse)
.then(function(salaries) {
return Promise.all(salaries.map(function(salary) {
// link salary to employee
return save(employee);
}));
});
}));
});
}));
});
In the innermost loop callback, you've got all of office, employee and salary available to interlink them to your liking. You cannot really avoid this kind of nesting.
You'll get back a promise for a huge array of arrays of arrays of save results, or for any error in the whole process.
It is good approach to change this
if (response.statusCode !== 200) {
reject(statusCode);
}
resolve(data);
to this
if (response.statusCode !== 200) {
return reject(statusCode);
}
resolve(data);
In your example, the result will be same, but if you are making more things (like doing something in database) the unexpected result may occure, because without return the whole method will be executed.
This example
var prom = new Promise((resolve,reject) => {
reject(new Error('error'));
console.log('What? It did not end');
resolve('Ok, promise will not be called twice');
});
prom.then(val => {
console.log(val);
}).catch(err => {
console.log(err.message);
});
is having this output
What? It did not end
error
To the question - if you need access to more than one returned value (i.e. offices AND employies), you have basically two options :
Nested promises - this is not generally bad, if it "makes sense". Altought promises are great to avoid huge callback nesting, it is ok to nest the promises, if the logic needs it.
Having "global" variables - you can define variable in the scope of the promise itself and save results to it, therefore the promises are using these variables as "global" (in their scope).

Categories

Resources