Why can't I use then on a resolved promise? - javascript

I have my function whose job is to go over a number of files (that use the values from the array as building blocks for file names) and download them using a reduce. It's more of a hack as of now but the Promise logic should work. Except it doesn.t
Here's my code:
function import_demo_files(data) {
/**
* Make a local copy of the passed data.
*/
let request_data = $.extend({}, data);
const get_number_of_files_1 = Promise.resolve({
'data' : {
'number_of_files' : 2
}
});
return new Promise((resolve, reject) => {
let import_files = get_number_of_files_1.then(function(response) {
new Array(response.data.number_of_files).fill(request_data.step_name).reduce((previous_promise, next_step_identifier) => {
let file_counter = 1;
return previous_promise.then((response) => {
if( response !== undefined ) {
if('finished_import' in response.data && response.data.finished_import === true || response.success === false) {
return import_files;
}
}
const recursively_install_step_file = () => import_demo_file({
demo_handle: request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': file_counter
}).call().then(function(response) {
file_counter++;
if('file_counter' in response.data && 'needs_resume' in response.data) {
if(response.data.needs_resume === true) {
file_counter = response.data.file_counter;
}
}
return response.data.keep_importing_more_files === true ? recursively_install_step_file() : response
});
return recursively_install_step_file();
}).catch(function(error) {
reject(error);
});
}, Promise.resolve())
}).catch(function(error) {
reject(error);
});
resolve(import_files);
});
}
Now, when I do:
const import_call = import_demo_files({ 'demo_handle' : 'demo-2', 'step_name' : 'post' });
console.log(import_call);
The console.log gives me back that import_call is, in fact a promise and it's resolved. I very much like the way return allows me to bail out of a promise-chain, but I have no idea how to properly resolve my promise chain in there, so clearly, it's marked as resolved when it isn't.
I would like to do import_call.then(... but that doesn't work as of now, it executes this code in here before it's actually done because of the improper handling in import_demo_files.

An asynchronous recursion inside a reduction isn't the simplest of things to cut your teeth on, and it's not immediately obvious why you would want to given that each iteration of the recursion is identical to every other iteration.
The reduce/recurse pattern is simpler to understand with the following pulled out, as outer members :
1. the `recursively_install_step_file()` function
1. the `new Array(...).fill(...)`, as `starterArray`
1. the object passed repeatedly to `import_demo_file()`, as `importOptions`)
This approach obviates the need for the variable file_counter, since importOptions.file_counter can be updated directly.
function import_demo_files(data) {
// outer members
let request_data = $.extend({}, data);
const importOptions = {
'demo_handle': request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': 1
};
const starterArray = new Array(2).fill(request_data.step_name);
function recursively_install_step_file() {
return import_demo_file(importOptions).then((res) => {
if('file_counter' in res.data && 'needs_resume' in res.data && res.data.needs_resume) {
importOptions.file_counter = res.data.file_counter; // should = be += ?
} else {
importOptions.file_counter++;
}
return res.data.keep_importing_more_files ? recursively_install_step_file() : res;
});
}
// the reduce/recurse pattern
return starterArray.reduce((previous_promise, next_step_identifier) => { // next_step_identifier is not used?
let importOptions.file_counter = 1; // reset to 1 at each stage of the reduction?
return previous_promise.then(response => {
if(response && ('finished_import' in response.data && response.data.finished_import || !response.success)) {
return response;
} else {
return recursively_install_step_file(); // execution will drop through to here on first iteration of the reduction
}
});
}, Promise.resolve());
}
May not be 100% correct but the overall pattern should be about right. Be prepared to work on it some.

Related

Adapting a function that isnt chainable to return a value

I am trying to get all the pages of a pdf in one object using the pdfreader package. The function originally returns each page (as its own object) when it processes it. My goal is to write a wrapper that returns all pages as an array of page objects. Can someone explain why this didn't work?
I tried:
adding .then and a return condition - because I expected the parseFileItems method to return a value:
let pages = [];
new pdfreader.PdfReader()
.parseFileItems(pp, function(err, item) {
{
if (!item) {
return pages;
} else if (item.page) {
pages.push(lines);
rows = {};
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
}
})
.then(() => {
console.log("done" + pages.length);
});
and got the error
TypeError: Cannot read property 'then' of undefined
The function I'm modifying (From the package documentation):
var pdfreader = require("pdfreader");
var rows = {}; // indexed by y-position
function printRows() {
Object.keys(rows) // => array of y-positions (type: float)
.sort((y1, y2) => parseFloat(y1) - parseFloat(y2)) // sort float positions
.forEach(y => console.log((rows[y] || []).join("")));
}
new pdfreader.PdfReader().parseFileItems("CV_ErhanYasar.pdf", function(
err,
item
) {
if (!item || item.page) {
// end of file, or page
printRows();
console.log("PAGE:", item.page);
rows = {}; // clear rows for next page
} else if (item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
});
There seem to be several issues/misconceptions at once here. Let's try to look at them once at a time.
Firstly, you seem to have thought that the outer function will return ("pass on") your callback's return value
This is not the case as you can see in the library source.
Also, it wouldn't even make sense, because the callback called once for each item. So, with 10 items, it will be invoked 10 times, and then how would parseFileItems know which of the 10 return values of your callback to pass to the outside?
It doesn't matter what you return from the callback function, as the parseFileItems function simply ignores it. Furthermore, the parseFileItems function itself doesn't return anything either. So, the result of new pdfreader.parseFileItems(...) will always evaluate to undefined (and undefined obviously has no property then).
Secondly, you seem to have thought that .then is some sort of universal chaining method for function calls.
In fact, .then is a way to chain promises, or to react on the fulfillment of a promise. In this case, there are no promises anywhere, and in particular parseFileItems doesn't returns a promise (it returns undefined as described above), so you cannot call .then on its result.
According to the docs, you are supposed to react on errors and the end of the stream yourself. So, your code would work like this:
let pages = [];
new pdfreader.PdfReader()
.parseFileItems(pp, function(err, item) {
{
if (!item) {
// ****** Here we are done! ******
console.log("done" + pages.length) // The code that was in the `then` goes here instead
} else if (item.page) {
pages.push(lines);
rows = {};
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text);
}
}
})
However, I agree that it'd be nicer to have a promise wrapper so that you won't have to stuff all the following code inside the callback's if (!item) branch. You could achieve that like this, using new Promise:
const promisifiedParseFileItems = (pp, itemHandler) => new Promise((resolve, reject) => {
new pdfreader.PdfReader().parseFileItems(pp, (err, item) => {
if (err) {
reject(err)
} else if (!item) {
resolve()
} else {
itemHandler(item)
}
})
})
let pages = []
promisifiedParseFileItems(pp, item => {
if (item.page) {
pages.push(lines)
rows = {}
} else if (item && item.text) {
// accumulate text items into rows object, per line
(rows[item.y] = rows[item.y] || []).push(item.text)
}
}).then(() => {
console.log("done", pages.length)
}, e => {
console.error("error", e)
})
Note: You would get even nicer code with async generators but that is too much to explain here now, because the conversion from a callback to an async generator is less trivial than you may think.
If you want to chain a then, you need the callback function to return a Promise :
new pdfreader.PdfReader()
.parseFileItems(pp, function (err, item) {
return new Promise( (resolve, reject) => {
let pages = ...
// do stuff
resolve(pages);
}
})
.then( pages => {
console.log("done" + pages.length);
});

How to I make an inner promise finish looping before checking a condition?

I am still new to Promises and async coding in JavaScript. I am trying to create a function that returns a promise that iterate through an array of objects with a setTimeout. On each element, I will pass it to another function that returns a Promise. If the element doesn't satisfy a condition, I put it into another array and pass that new array into the function for a recursive call 10 more times until it satisfy the condition. Here is the code:
const promiseFunc = (item) => {
return new Promise((resolve, reject) => {
// Do something
if (some_kind_of_error) {
return reject(the_error);
} else {
return resolve({
itemName: item.name,
status: (item.isComplete === 'complete')
});
}
});
};
const func2 = (listOfItems, count) => {
return new Promise((resolve, reject) => {
if (count > 10) {
reject(new Error("Too many attempts."));
}
setTimeout(() => {
const newList = [];
listOfItems.forEach(item => {
promiseFunc(item)
.then(result => {
if(result.isCompleted !== true) {
newList.push(item);
}
});
});
if (newList.length === 0) {
return resolve(true);
} else {
console.log('Calling func2 again');
return func2(newList, count+1);
}
}, 1000);
});
};
The problem is that when I run the func2 function, I always get true even if it is suppose to recurse.
When I tried to log things out, I notice that the message Calling func2 again was not logged out in the terminal. This means that no matter what, the condition for checking newList will always be empty hence it is always resolving true and never going to the else statement.
Can someone please explain why this is the current behavior? How do I make it so that my func2 will wait for the execution of if (newList.length === 0) until my forEach loop is done?

Avoid callback hell and organising Node.js code

i am trying to orgainse my code and wanted to create separte function for every .then(),some how i am unable to do,and my code break
please help me how to make things working
module.exports = function () {
return new Promise((resolve, reject) => {
try {
const settings = blob();
var {
someObject
} = JSON.parse(requestBody);
var var1,var2,var3
let somePromises = [];
someObject.forEach((p) => {
p.somepro = 'anything';
});
Promise.all(somePromises)
.then((res) => {
//replace cart item info
res.forEach((r) => {
someObject.forEach((so) => {
so.info = ''
});
});
});
return require('/file1')(); // api call 1
})
.then((res) => {
var2 = resp.something // local variable create above var2
return require('/file2')(); // api call 2
})
.then((res) => {
var3 = resp.something // local variable create above var3
return require('/file2')(); // api call 3
})
.then((r) => {
// some other maniuplation
})
.then(() => {
// some calulation based on above responses and local variable
// assigned
resolve({
someObject,
var1,
var2
});
});
} catch (e) {
reject(e);
}
});
};
i am trying to make the code organise and create separate function for every promise but not getting and confused how can i create this flow in an organised and best practise ways
First of all do not resolve objects, it's not that safe, because the then property is being used by the Promise if it's a function.
Objects returned like that do show detailed(with variable name) when console.loging ...
But let's assume that until your promise is resolving your object, some method adds/replaces then in your object with a method, then you'll have some debugging to do on those promises.
Read more on thenable objects here
i am trying to orgainse my code and wanted to create separte function for every .then()
I created a custom method, that passes the Promise.all values in order for each .then.
Your request is to create separate function for every .then.
Just copy paste useIfFor method and rename/change it as you wish.
PS: Some chunks from your code still resides there ... they're harmless.
console.clear();
let module = {};
module.exports = () => {
return new Promise((resolve, reject) => {
const settings = {}; //blob();
var {
someObject
} = JSON.parse(typeof requestBody !== 'undefined' ? requestBody : '[]');
let somePromises = [
Promise.resolve('some text'),
Promise.resolve(100),
Promise.resolve(10000),
];
// var var1, var2, var3
let stackVariables = [];
// It passes the first value from Promise.all to the first 'then'
// the 2nd value to the 2nd `then`
// the 3rd value to the 3rd `then`
// ...
// the N-th value to the N-th `then`
const useIfFor = (someStringOrNumber) => {
return (res) => {
// We'll use the first value of `res` and pass the rest of the values to the next `then`
let [promiseValue, ...restOfTheValues] = res;
// START HERE - To add your logic - `promiseValue` is your old 'res'
console.log('Current `then` value:', promiseValue, '| Label:', someStringOrNumber);
if (someStringOrNumber === 'my-first-then') {
promiseValue = 'THIS VALUE IS MODIFIED';
stackVariables.push(promiseValue); // first value from Promise.all
} else if (someStringOrNumber === 'my-second-then') {
stackVariables.push(promiseValue); // second value from Promise.all
} else if (someStringOrNumber === 'my-third-then') {
stackVariables.push(promiseValue); // third value from Promise.all
} else {
// You can end it with resolve anywhere
//resolve(stackVariables);
}
// END HERE
if (typeof promiseValue === 'undefined') {
// You reached the end, no more values.
resolve(stackVariables);
}
// Passing the remaining values to the next `then`
return restOfTheValues;
}
}
Promise.all(somePromises)
.then(useIfFor('my-first-then'))
.then(useIfFor('my-second-then'))
.then(useIfFor('my-third-then'))
.then(useIfFor('done')) // <- here is the resolve because there isn't the 4th promise, therefore, no values
.catch((err) => {
reject(err);
})
});
};
(module.exports)()
.then(res => {
console.log('res', res);
}).catch(console.error);

Jump out of a promise chain or take a different path

I have an issue where I may need to jump out of the whole promise chain because of some value or take two paths based on a value.
How best do you do that?
Here is the first scenario where I would like to just jump out of the whole chain. I just want to give them a message.
DB_WorkIssues.info().then(function (details) {
if (details.doc_count == 0 && details.update_seq == 0) {
showMsg("This device has no local data on it and no access to the Server, please come back when you are online.")
} jump out here, no need to do the next set.
else
return; Continue on as the values are valid.
}).then(function () {
return ajaxCallForJson(URI_LookupTables);
}).then(function (json) {
return callBulkDocLoad(DB_LookupTables, json);
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
In the 2nd scenario, I may want to take one path if the values are one thing and another if the values are another. But I still want the chains to work with the first promise. Something like this:
DB_WorkIssues.info().then(function (details) {
if (details.doc_count == 0 && details.update_seq == 0) {
Take this path.
return;
}).then(function () {
return ajaxCallForJson(URI_LookupTables);
}).then(function (json) {
return callBulkDocLoad(DB_LookupTables, json);
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
}
else
{
Take this path instead
return;
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
}
Thanks.
Here is what I was thinking after looking at the answer where I do the second promise always and only do the first in some cases.
DB_WorkIssues.info().then(function(details) {
// promise variable , defined in conditional
var promise;
Would I set the promise to some default value, in case the following test fails
if (details.doc_count == 0 && details.update_seq == 0) {
// return this promise
promise = ajaxCallForJson(URI_LookupTables).then(function(json) {
return callBulkDocLoad(DB_LookupTables, json);
});
}
return promise;
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).then(function () {
return loadLocationsDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
Is that how I could do it?
Thanks.
I think this is a skeleton that represents what you're going for. Promises are incredibly powerful and worth studying. I tried to add helpful comments but I suggest playing around with the code and understanding what's going on.
// Six named promise-creators. When called with (x), will create a promise
// which waits 200ms and then logs and resolves with (x).
// These could represent any asynchronous operation.
const p1 = p2 = p3 = p4 = p5 = p6 =
(x) => {
const p = new Promise((resolve, reject) => {
setTimeout(() => {resolve(x); console.log(x)}, 200)
});
return p;
}
// A function which, when called, will execute first promise chain.
const first_steps = () =>
p1(1)
.then(result => p2(2))
.then(result => p3(3))
// A function which, when called, will execute second promise chain.
const second_steps = () =>
p4(4)
.then(result => p5(5))
.then(result => p6(6))
// When true, this prints numbers 1-6.
// When false, only prints numbers 4-6.
if (false) {
console.log(first_steps().then(second_steps));
} else {
second_steps();
}
Seems to me you have extra sets of then() and your if() would determine which promise to return something like:
DB_WorkIssues.info().then(function(details) {
// promise variable , defined in conditional
var promise;
if (details.doc_count == 0 && details.update_seq == 0) {
// return this promise
promise = ajaxCallForJson(URI_LookupTables).then(function(json) {
return callBulkDocLoad(DB_LookupTables, json);
});
} else {
// or this promise
promise = loadCategoriesDDL().then(function() {
return loadEquipmentDDL();
});
}
promise.catch(function(err) {
showMsg("Error in defineDBs: " + err);
});
return promise;
})

Callback after iterating through an array and saving objects

I need to iterate through an array and save every object to the Database.
At the end I need a callback with an array of all of the saved and failed object.
Below is the code I have:
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId')
db.List.create(element).then((list) => {
savedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
})
})
}
The code above works. Is there a way better to accomplish this?
I would recommend the following approach using Promise.all() to run the db.List.create() in parallel as it will return a Promise. By mapping the body array elements to Promises you can achieve better performance as they will run in parallel (and not have to track the complete count).
exports.addList = (app, body, callback) => {
var savedObjects = [];
var failedObjects = [];
Promise.all(
// map the array to return Promises
body.map(element => {
const list = _.pick(element, 'userAId','userBId');
return db.List.create(list)
.then(() => savedObjects.push(list))
.catch((error) => {
if (error.name === 'SequelizeUniqueConstraintError') {
failedObjects.push(list)
}
})
})
)
// when all Promises have resolved return the callback
.then(() => callback(savedObjects, failedObjects));
}
In your example your complete callback will always fire after the first promise is complete. This is because the create function is asynchronous while the surrounding loop is not, therefore the loop will have already completed by the time your first callback is triggered.
In your scenario this means element and index will always be the last in the loop. One way around this would be to move your promise chain into it's own function.
In this example I've used an extra flag to track the number of completed promises to trigger your complete method.
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
var complete = 0;
function createElement(element){
db.List.create(element).then((list) => {
savedObjects.push(element)
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
}
}).finally(() => {
complete++;
if(complete == body.length) {
callback(savedObjects, failedObjects)
}
});
}
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId');
createElement(element);
})
}

Categories