nodejs then() function executed before promise resolve - javascript

I have a problem with making Promise working as expected. I need to do following thing:
I get file names from stdout, split them into line and copy them. When copy operation is finished i want to start other operations and here is my problem.
I've created a copy function inside Promise, in case of error i reject it immediately, if thereare no errors i resolve it after copy in loop is finished but for some reason the function inside then() gets executed before copy operation is done
var lines = stdout.split(/\r?\n/);
copyUpdatedFiles(lines).then(
function() {
console.log('this one should be executed after copy operation');
}
);
function copyUpdatedFiles(lines) {
return new Promise(function(resolve, reject) {
for (var i = 0; i < linesLength; i++) {
fs.copy(lines[i], target, function(err) {
if (err) {
reject();
}
});
}
resolve();
});
}
Please help cause i'm clearly missing something.

It gets resolved as soon as you call resolve, which you're doing after starting the copies but before they finish. You have to wait for the last callback before you resolve. That means keeping track of how many you've see, see the *** comments:
function copyUpdatedFiles(lines) {
return new Promise(function(resolve, reject) {
var callbacks = 0; // ***
for (var i = 0; i < linesLength; i++) {
fs.copy(lines[i], target, function(err) {
if (err) {
reject();
} else { // ***
if (++callbacks == lines.length) { // ***
resolve(); // ***
} // ***
} // ***
});
}
});
}
Alternately, there are a couple of libraries out there that promise-ify NodeJS-style callbacks so you can use standard promise composition techniques like Promise.all. If you were using one of those, you'd just do something this:
function copyUpdatedFiles(lines) {
return Promise.all(
// CONCEPTUAL, semantics will depend on the promise wrapper lib
lines.map(line => thePromiseWrapper(fs.copy, line, target))
);
}
Side note: Your loop condition refers to a variable linesLength that isn't defined anywhere in your code. It should be lines.length.

You don't wait for the copy to success before resolving the promise, after the for, all fs.copy has been put in the call stack, but they didn't complete.
You can either use a counter inside the callback of fs.copy and call resolve once every callback has been called, or use async.
var async = require('async');
var lines = stdout.split(/\r?\n/);
copyUpdatedFiles(lines).then(
function() {
console.log('this one should be executed after copy operation');
}
);
function copyUpdatedFiles(lines) {
return new Promise(function(resolve, reject) {
async.map(lines, (line, callback) => {
fs.copy(line, target, (err) => {
callback(err);
});
},
(err) => {
if(err) {
reject();
} else {
resolve();
}
});
});
}

Related

Directory check returning false in node.js

I'm having an issue with a Node.js function. I'm fairly certain that it is just an issue with the function being asynchronous but I want to be sure. I am checking to see if a certain path that was entered by the user is valid by checking if it exists.
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
if (fs.statSync(filePath).isDirectory()){
resolve("Valid");
} else {
reject("Invalid");
}
});
}
These are my calls to the function:
files.directoryExists(sourcePath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
files.directoryExists(destPath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
I'm very new to the whole concept of asynchronous programming and promises so this is becoming quite frustrating. Any help would be appreciated.
It's not really the asynchronous thing that's catching you out, although there's a change you can make to improve that.
statSync can throw an exception (if the path doesn't match anything, for instance); you're not handling that, and so when it throws, that gets converted into a rejection. If you looked at the argument you're getting in your catch handler, you'd see the exception that it raises.
The async improvement is that since you're using a Promise, there's no reason to use statSync. Just use stat so you don't tie up the JavaScript thread unnecessarily.
So something along the lines of:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
// If there was an error or it wasn't a directory...
if (err || !data.isDirectory()) {
// ...reject
reject(err || new Error("Not a directory");
} else {
// All good
resolve(data);
}
});
});
};
Of course, you may choose to have it resolve with false if the thing isn't a directory or any of several other choices; this just gets you further along.
For instance, having an error still be a rejection, but resolving with true/false for whether something that exists is a directory; this provides the maximum amount of information to the caller but makes them work a bit harder if all they care about is true/false:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
if (err) {
// Reject on error
reject(err);
} else {
// Return result on success
resolve(data.isDirectory());
}
});
});
};
or making it always resolve, with false if there's no match or there is a match but it's not a directory:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
resolve(!err && data.isDirectory());
});
});
};
Lots of ways for this function to behave, it's up to you.

JavaScript: Asynchronous method in while loop

I'm tackling a project that requires me to use JavaScript with an API method call. I'm a Java programmer who has never done web development before so I'm having some trouble with it.
This API method is asynchronous and it's in a while loop. If it returns an empty array, the while loop finishes. Otherwise, it loops. Code:
var done = true;
do
{
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// Sets the boolean to true if the returned array is empty, or false otherwise.
done = (result.data().length === 0) ? true : false;
}
}
);
} while (!done);
This doesn't work. The loop ends before the value of "done" is updated. I've done some reading up on the subject and it appears I need to use promises or callbacks because the API call is asynchronous, but I can't understand how to apply them to the code I have above.
Help would be appreciated!
edit: see the bottom, there is the real answer.
I encourage you yo use the Promise API. Your problem can be solved using a Promise.all call:
let promises = [];
while(something){
promises.push(new Promise((r, j) => {
YourAsyncCall(() => r());
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(() => {
//All operations done
});
The syntax is in es6, here is the es5 equivalent (Promise API may be included externally):
var promises = [];
while(something){
promises.push(new Promise(function(r, j){
YourAsyncCall(function(){ r(); });
});
}
//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(function(){
//All operations done
});
You can also make your api call return the promise and push it directly to the promise array.
If you don't want to edit the api_call_method you can always wrap your code in a new promise and call the method resolve when it finishes.
edit: I have seen now the point of your code, sorry. I've just realized that Promise.all will not solve the problem.
You shall put what you posted (excluding the while loop and the control value) inside a function, and depending on the condition calling it again.
Then, all can be wraped inside a promise in order to make the external code aware of this asynchronous execution. I'll post some sample code later with my PC.
So the good answer
You can use a promise to control the flow of your application and use recursion instead of the while loop:
function asyncOp(resolve, reject) {
//If you're using NodeJS you can use Es6 syntax:
async_api_call("method.name", {}, (result) => {
if(result.error()) {
console.error(result.error());
reject(result.error()); //You can reject the promise, this is optional.
} else {
//If your operation succeeds, resolve the promise and don't call again.
if (result.data().length === 0) {
asyncOp(resolve); //Try again
} else {
resolve(result); //Resolve the promise, pass the result.
}
}
});
}
new Promise((r, j) => {
asyncOp(r, j);
}).then((result) => {
//This will call if your algorithm succeeds!
});
/*
* Please note that "(...) => {}" equivals to "function(...){}"
*/
sigmasoldier's solution is correct, just wanted to share the ES6 version with async / await:
const asyncFunction = (t) => new Promise(resolve => setTimeout(resolve, t));
const getData = async (resolve, reject, count) => {
console.log('waiting');
await asyncFunction(3000);
console.log('finshed waiting');
count++;
if (count < 2) {
getData(resolve, reject, count);
} else {
return resolve();
}
}
const runScript = async () => {
await new Promise((r, j) => getData(r, j, 0));
console.log('finished');
};
runScript();
If you don't want to use recursion you can change your while loop into a for of loop and use a generator function for maintaining done state. Here's a simple example where the for of loop will wait for the async function until we've had 5 iterations and then done is flipped to true. You should be able to update this concept to set your done variable to true when your webservice calls have buffered all of your data rows.
let done = false;
let count = 0;
const whileGenerator = function* () {
while (!done) {
yield count;
}
};
const asyncFunction = async function(){
await new Promise(resolve => { setTimeout(resolve); });
};
const main = new Promise(async (resolve)=>{
for (let i of whileGenerator()){
console.log(i);
await asyncFunction();
count++;
if (count === 5){
done = true;
}
}
resolve();
});
main.then(()=>{
console.log('all done!');
});
Also you may try recursion solution.
function asyncCall(cb) {
// Some async operation
}
function responseHandler(result) {
if (result.error()) {
console.error(result.error());
} else if(result.data() && result.data().length) {
asyncCall(responseHandler);
}
}
asyncCall(responseHandler);
Here is a solution I came up with. Place this in an async function.
let finished = false;
const loop = async () => {
return new Promise(async (resolve, reject) => {
const inner = async () => {
if (!finished) {
//insert loop code here
if (xxx is done) { //insert this in your loop code after task is complete
finshed = true;
resolve();
} else {
return inner();
}
}
}
await inner();
})
}
await loop();
If you don't want to use Promises you can restructure your code like so:
var tasks = [];
var index = 0;
function processNextTask()
{
if(++index == tasks.length)
{
// no more tasks
return;
}
async_api_call(
"method.name",
{
// Do stuff.
},
function(result)
{
if(result.error())
{
console.error(result.error());
}
else
{
// process data
setTimeout(processNextTask);
}
}
);
}
Your loop won't work, because it is sync, your async task is async, so the loop will finish before the async task can even respond. I'd reccomend you to use Promises to manage async tasks:
//first wrapping your API into a promise
var async_api_call_promise = function(methodName, someObject){
return new Promise((resolve, reject) => {
async_api_call(methodName, someObject, function(result){
if(result.error()){
reject( result.error() )
}else{
resolve( result.data() )
}
});
})
}
now to your polling code:
//a local utility because I don't want to repeat myself
var poll = () => async_api_call_promise("method.name", {/*Do stuff.*/});
//your pulling operation
poll().then(
data => data.length === 0 || poll(), //true || tryAgain
err => {
console.error(err);
return poll();
}
).then((done) => {
//done === true
//here you put the code that has to wait for your "loop" to finish
});
Why Promises? Because they do state-management of async operations. Why implement that yourself?
let taskPool = new Promise(function(resolve, reject) {
resolve("Success!");
});
let that = this;
while (index < this.totalPieces) {
end = start + thisPartSize;
if (end > filesize) {
end = filesize;
thisPartSize = filesize - start;
}
taskPool.then(() => {
that.worker(start, end, index, thisPartSize);
});
index++;
start = end;
}

Promises not working if PouchDB call in promise

I promises and I cannot find anything that handles my scenario.
I used a sample set of promises that work perfect until I add the pouch allDocs call in.
It works fine when I chain directly on the allDocs call. I was told I could put a promise in a promise but that does not seem to be the case.
The reason I have it set up the way I do and why it would be difficult to use Promise.All() is because I don't want the promises executing all at onece. I want one promise to follow another.
I stripped everything out only showing one promise that has PouchDB call (allDocs) in it. Maybe this cannot be done.
Here is my promise with console.logs in them so you can see the path.
My promise:
let cleanRoom = function () {
return new Promise(function (resolve, reject) {
console.log("starting cleanRoom");
console.log("starting DB_WorkIssue.alldocs");
DB_WorkIssues.allDocs({ include_docs: true, descending: false }, function (err, response) {
data = response.rows;
itemp = 0;
console.log("at For Loop in allDocs");
for (var i = 0; i < response.total_rows; i++) {
if (data[i].doc.IsDeleted || data[i].doc.IsWorkIssueInserted || data[i].doc.IsWorkIssueUpdated || data[i].doc.IsLogInserted) {
DirtyFlag = true;
}
}
console.log("exiting allDocs");
return;
}).then(function () {
console.log("inside then function after alldocs");
}).catch(function (err) {
console.log("inside catch");
showMsg("Error in cleanRoom/allDocs: " + err);
});
console.log("exiting cleanRoom")
resolve('Cleaned The Room');
});
};
All it does is call the allDocs method and then in the function look to see if any records have been updated. Then it can continue on.
But that is not what is happening.
Here is the chain:
}).then(function () {
return initialize();
}).then(function (prMessage) {
**return cleanRoom();** <--- my promise
}).then(function (result) {
return removeGarbage(result);
}).then(function (result) {
return winIcecream(result);
}).then(function (result) {
console.log('finished ' + result);
}).then(function () {
console.log("starting UpdateworkIssuesJson");
return updateWorkIssuesJson();
}).then(function () {
the results:
tasksmain.js:165 starting cleanRoom
tasksmain.js:166 starting DB_WorkIssue.alldocs <-- Pouch call
tasksmain.js:188 exiting cleanroom <-- exits promise
tasksmain.js:195 inside removeGarbage <-- I don't want it to do this until Pouch call is done.
tasksmain.js:202 inside winIceCream
taskspouch.js:91 finished Cleaned The Room remove Garbage won Icecream
taskspouch.js:93 starting UpdateworkIssuesJson
tasksmain.js:182 inside then function after alldocs <-- now back to the pouch function call (way too late.
tasksmain.js:171 at For Loop in allDocs
tasksmain.js:178 exiting allDocs <--- Pouches chain
taskspouch.js:96 starting showTasks
The question is - the original promise chain is working as it is supposed to.
It continues on when the "cleanroom" resolves. But the cleanroom resolves before the Pouch command is finished. It actually finishes when the allDoc
Is there a way to prevent that?
You forgot to resolve with the inner promise.
Just do resolve(DB_WorkIssues.allDocs({ include_docs: true, descending: false },…) instead of resolving the promise with a string (which immediately resolves the promise).

node.js promises not forcing order execution of functions

I have three functions that I want to use promises to force them to execute in order.
function 1 sends a http request, fetches JSON data and saved it to a file
function 2 loops through that file and updates the database according the difference values/values missing
function 3 will loop through the newly updated database and create a 2nd json file.
Currently function 1 works perfectly on its own with a setInterval of 30 minutes.
I want to start function 2 when function 1 has finished. then function 3 after function 2 has finished.
Using promises I am trying to attach function 2 to a simple finished log to understand how to use promises but not getting much success. The items from the for loop log but my Finished/err log before my for loop which shouldn't be happening. Any suggestions?
function readJson() {
return new Promise(function() {
fs.readFile(__dirname + "/" + "bitSkin.json", 'utf8', function read(err, data) {
if (err) { throw err; }
var bitCon = JSON.parse(data);
for(var i=0; i<7; i++) { //bitCon.prices.length; i++) {
var price = bitCon.prices[i].price
var itemName = bitCon.prices[i].market_hash_name;
(function() {
var iNameCopy = itemName;
var priceCopy = price;
logger.info(iNameCopy);
}());
}
});
});
};
function fin() {
logger.info("Finished");
}
readJson().then(fin(), console.log("err"));
Promises have no magical powers. They don't magically know when async code inside them is done. If you create a promise, you yourself have to resolve() or reject() it when the async code has an error or completes.
Then, in addition, you have to pass a function reference to a .then() handler, not the result of executing a function. .then(fin()) will call fin() immediately and pass it's return value to .then() which is not what you want. You want something like .then(fin).
Here's how you can resolve and reject the promise you created:
function readJson() {
return new Promise(function(resolve, reject) {
fs.readFile(__dirname + "/" + "bitSkin.json", 'utf8', function read(err, data) {
if (err) { return reject(err); }
var bitCon = JSON.parse(data);
for(var i=0; i<7; i++) { //bitCon.prices.length; i++) {
var price = bitCon.prices[i].price
var itemName = bitCon.prices[i].market_hash_name;
(function() {
var iNameCopy = itemName;
var priceCopy = price;
logger.info(iNameCopy);
}());
}
resolve(bitCon);
});
});
};
And, you could use that like this:
function fin() {
logger.info("Finished");
}
readJson().then(fin, function(err) {
console.log("err", err)
});
Summary of changes:
Added resolve, reject arguments to Promise callback so we can use them
Called reject(err) when there's an error
Called resolve() when the async code is done.
Passed a function reference for both .then() handlers.
FYI, when creating a promise wrapper around an async function, it is generally better to wrap just the function itself. This makes the wrapper 100% reusable and puts more of your code in the promise architecture which generally streamlines things and makes error handling easier. You could fix things up that way like this:
fs.readFilePromise = function(file, options) {
return new Promise(function(resolve, reject) {
fs.readFile(file, options, function(err, data) {
if (err) return reject(err);
resolve(data);
});
});
};
function readJson() {
return fs.readFilePromise(__dirname + "/" + "bitSkin.json", 'utf8').then(function(data) {
var bitCon = JSON.parse(data);
bitCon.prices.forEach(function(item) {
logger.info(item.market_hash_name);
});
return bitCon;
});
}

Waiting on asynchronous functions within multiple conditionals

The meteor project I'm working on uploads files when certain conditions are satisfied. Regardless of whether or not the files are uploaded, a Meteor.call has to be made once the if statements have completed. Because of the conditionals, when I use callbacks it results in a lot of duplicate code. As it is written below, I expect the Meteor.call could be executed before the uploadFile callbacks get executed which would be a problem.
var data = {
name: "..."
//...
}
if(condition){
uploadFile(parameters, function(error,result){
if(err) handleError(err);
else data.url1 = result.secure_url;
}
if(condition2){
uploadFile(parameters, function(error,result){
if(err) handleError(err);
else data.url2 = result.secure_url;
}
/* This Meteor.call needs to wait until both if statements above
have completed */
Meteor.call('insertData', data, function(error,result){
//...
}
you could remove the conditionals and instead implement Javascript Promises they're fun and fancy plus they eliminate callback hell and provide a readable top down format:
http://jsfiddle.net/4v29u4do/1/
This fiddle shows how you could use promises to wait for async callbacks instead of conditional if statements
With promises you can return a new promise as the callback from a promise, and keep going and going and the callback will get passed to the new .then until you're done if there is any errors along the way, it skips right to the .catch.
function uploadFile(file) {
return new Promise(function(resolve, reject) {
// Simulate ASYNC Call for Uploading File
setTimeout(function() {
console.log(file + ' Uploaded Successfully!');
return resolve(file);
// if (err) {
// return reject(err);
// }
}, 3000);
});
}
var data = {};
uploadFile("filename1")
.then(function(cb) {
data.url1 = cb;
return uploadFile("filename2");
})
.then(function(cb) {
data.url2 = cb;
return uploadFile("filename5");
})
.then(function(cb) {
data.url3 = cb;
console.log(data);
//all done with callbacks
// Meteor.call("");
})
.catch(function(err) {
// One of the uploads failed log the err;
});

Categories

Resources