Avoid callback hell and organising Node.js code - javascript

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);

Related

nodejs - change local variable value from within a event listener

Im trying to set a variable from within event handler/listener/function
export async function mis() {
let result; // <--------- LOCAL VARIABLE IM TRYING TO CHANGE (currently undefined)
const m = await spawn(`/cmd`);
m.stdout.on('data', function () {
result = true; // <---------- HERE IS WHERE IM TRING TO CHANGE LOCAL VARIABLE (set to true)
});
return result; // -------- RETURNING undefined instead of true
}
thanks in advance
As is currently written the code will executes sequentially and the function will not await the data and will reach the return statement before.
Using a Promise should work. Or you can also use util.promisify()
(Im not handling errors)
export async function mis() {
let result; // undefined
const m = await spawn(`/cmd`);
const onData = new Promise((resolve) => {
let data;
m.stdout.on('data', function () {
data = true;
resolve();
});
m.stdout.on('end', function() {
resolve(data); // resolve when the events end
})
})
const res = await onData();
return res;
}

Get properties from previously resolved promise?

I have an async function that returns an object after running fetch and .json(). I want to have more than one callback that take the object as input. Since I need to chain .then() to call the callback, I need to run the fetching function each time.
Is there a way to fetch once, and then have the callbacks do their thing with the output without having to refetch?
async function getData(){
const response = await fetch('api-url');
return await response.json();
}
function foo(data){
// do stuff
}
function bar(data){
// do stuff
}
const doFoo = getData().then(foo) // I don't want to run getData()
const doBar = getData().then(bar) // each time
I guess, I can save the output to a cache, but is there a way to do it with promises?
Any time you want to generate a value once and then use it multiple times: Store it in a variable.
const data = getData();
data.then(foo);
data.then(bar);
//to check the state of the promise
undefined === Promise.prototype.state && (Promise.prototype.state = function () {
const t = {};
return Promise.race([this, t]).then(v => (v === t) ? 'pending' : 'fulfilled', () => 'rejected');
});
function promiseWrapper(promiseFunction) {
let getPromise = Promise.reject();
let getFinalData = async function(force) {
let args = [].slice.call(arguments, 1);
//if force available, api will be called
if(force) {
return promiseFunction.apply(this, args);
}
let state = await this.getPromise.state();
//if state is rejected, trigger and fetch the data
'rejected' === state && (this.getPromise = promiseFunction.apply(this, args));
return this.getPromise;
}
//get method first method is force, rest will be treated as arguments
return {getPromise, get: getFinalData};
}
//instead of creating function pass the function in promiseWrapper
//promiseWrapper helps if somehow it is rejected, we wan recall the function without refreshing the page
var getData = promiseWrapper(async function() {
const response = await fetch('https://reqres.in/api/products/3');
return await response.json();
});
function foo(data) {
console.log('foo data\n', data)
}
function bar(data) {
console.log('bar data\n', data)
}
getData.get().then(foo) //It will trigger the api, because it is first time
getData.get(true).then(bar) //data by calling api
//getData.get(false).then(console.log) and getData.get().then(console.log), will act same

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);
});

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

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.

forEach function scoping - how to parse array to function without repeat function calls

I have a function that I call and within the function I use a promise, I also want to call this function ( it's actually the second function called in the code) first, retrieve some data, then parse this data through after my second function which calls out to also retrieve some data.
getAllProductGroups(): Promise<void> {
return new Promise((resolve, reject) => {
this.allEngagementTypes().subscribe(data => {
const products = {};
const potentialEvents = [];
data.forEach(product => {
// products[product.id] = product.description;
this.potentialForEvents(this.organization.id, product.id)
.subscribe(potentialEventData => {
potentialEvents.push(potentialEventData);
this.allEngagementAreas().subscribe(areas => {
this.organization.addAreas(areas, potentialEvents);
resolve();
}, error => reject(error));
});
});
})
});
}
I call the forEach on the first function call allEngagementTypes, as I need to use each element to then make my second function call on potentialForEvents, then I create an array with the responses with potentialEvents.push,
I want to then with this array, parse it after my third call 'allEngagementAreas' as when this calls on the 'addAreas' function, I want to parse the array potentialEvents, but because of the forEach It is sent 21 times, only two of which area actually sent before the 'addAreas' fully loads, I need just one array to be sent and for it to be parsed and be ready before the allEngagementAreas is actually called.
Any help is greatly appreciated.
if I understood your problem correctly:
getAllProductGroups(): Promise<void> {
return new Promise((resolve, reject) => {
this.allEngagementTypes().subscribe(data => {
const products = {};
const potentialEvents = [];
data.forEach(product => {
// products[product.id] = product.description;
this.potentialForEvents(this.organization.id, product.id)
.subscribe(potentialEventData => {
potentialEvents.push(potentialEventData);
if(potentialEvents.length === someConstValue) { //not sure how, but you need to find out the needed array length
resolve(potentialEvents);
}
});
});
})
}).then(potentialEvents => {
this.allEngagementAreas().subscribe(areas => {
this.organization.addAreas(areas, potentialEvents);
}, error => reject(error));
})
}
main idea is to split it to two promises, and resolve the first one when the array if filled. in forEach loops they usually compare the result length with some const value.

Categories