Promises chaining - javascript

I am begining in node and I have a problem, I need to do this only with promises by node versions, I need to make a comparison with the result of previous promises but dont works, it seems me that is code that is poorly structured , here the code thanks ...
ListarUsuariosMaxDate.then(response => {
return ListaconCondicionProceso(response);
}).then(data2 => {
return filtrarPagos.then(data3 => {
let result = data3.filter(elem => data2.filter(elem2 => elem.user == elem2.user_id && elem.createdAt > elem2.maximo).length > 0);
return result;
}).then(data5 => {
console.log(data5)
})
})
})
the result (data5) is emtpy...sorry about the mistake...

I could do it something change and works, but I don't understand why. I had in my previous code with async and await this structure, and it worked:
async function comparacionTotalProceso(listaTotal, listaFiltrada) {
let result = listaFiltrada.filter(elem => listaTotal.filter(elem2 => elem.user == elem2.user_id && elem.createdAt > elem2.maximo).length > 0);
return result;
});
}
but now for node version, I need to change everything to promises and I did this:
...
.then(data2 => {
return filtrarPagos.then(data3 => {
let result = data2.map(elem => data3.map(elem2 => elem.user == elem2.user_id && elem.createdAt > elem2.maximo).length > 0? elem:null);
return result;
})
and finally works changed the filter for map and return elem, but I don't know why.

Related

Cypress foreach loop stop condition

I need some help with the following loop:
localStorage.removeItem('DDFound');
cy.get(sel).each(($el, index, $list) => {
if(localStorage.getItem('DDFound')!='1')
{
cy.log(localStorage.getItem('DDFound'));
cy.wrap($el).click().then(()=> {
cy.get(table).then(elx => {
if(elx.find(tableitem).length > 0) {
cy.get(tableitem).click();
cy.get(lable).should('contain.text',"Item")
localStorage.setItem('DDFound','1');
}
})
});
}
});
I would like to break just after finding the right item(tableitem) and for that, I'm setting a localstorage (didn't find any other way) but it looks like cypress runs all items in each loop in parallel and not getting to the if(localStorage.getItem('DDFound')!='1') after each element.
You can stop the .each() loop early by returning false in the callback function. In your case, we can just return the value if DDItem does not equal one.
localStorage.removeItem('DDFound');
cy.get(sel).each(($el, index, $list) => {
if(localStorage.getItem('DDFound')!='1')
{
cy.log(localStorage.getItem('DDFound'));
cy.wrap($el).click().then(()=> {
cy.get(table).then(elx => {
if(elx.find(tableitem).length > 0) {
cy.get(tableitem).click();
cy.get(lable).should('contain.text',"Item")
localStorage.setItem('DDFound','1');
}
})
});
}
return localStorage.getItem('DDFound') !== 1
});
If you have complex code inside the .each() you should wrap it in a promise
let found = false;
cy.get(sel).each(($el, index, $list) => {
if (found) return false; // early exit
return new Cypress.Promise(resolve => {
cy.wrap($el).click().then(() => {
cy.get(table).then(elx => {
if (elx.find(tableitem).length > 0) {
cy.get(tableitem).click();
cy.get(label).should('contain.text',"Item")
found = true;
}
resolve() // wait for above to complete
})
})
})
})
See .each() - Promises
Promises are awaited
If your callback function returns a Promise, it will be awaited before iterating over the next element in the collection.

How to count the number of promises that have failed?

I'm working on a small Javascript application that uses an API from pokeapi.co. Basically, it is supposed to fetch a few datas from the API and then display them on an HTML page. Here is the main part of the code :
const searchInput= document.querySelector(".recherche-poke input");
let allPokemon= [];
let tableauFin= [];
const listePoke= document.querySelector('.liste-poke');
function fetchPokemonBase(){
fetch("https://pokeapi.co/api/v2/pokemon?limit=75")
.then(reponse => reponse.json())
.then((allPoke) =>{
allPoke.results.forEach((pokemon) =>{
fetchPokemonComplet(pokemon);
})
})
}
fetchPokemonBase();
function fetchPokemonComplet(pokemon){
let objPokemonFull = {};
let url = pokemon.url;
let nameP = pokemon.name;
fetch(url)
.then(reponse => reponse.json())
.then((pokeData) => {
objPokemonFull.pic = pokeData.sprites.front_default;
objPokemonFull.type = pokeData.types[0].type.name;
objPokemonFull.id = pokeData.id;
fetch(`https://pokeapi.co/api/v2/pokemon-species/${nameP}`)
.then(reponse => reponse.json())
.then((pokeData) => {
objPokemonFull.name= pokeData.names[4].name;
allPokemon.push(objPokemonFull);
if(allPokemon.length === 75){
tableauFin= allPokemon.sort((a, b) => {
return a.id - b.id;
}).slice(0, 21);
createCard(tableauFin);
}
})
});
}
function createCard(arr){
for(let i= 0; i< arr.length; i++){
console.log(i + '\n');
const carte= document.createElement("li");
const txtCarte= document.createElement('h5');
txtCarte.innerText= arr[i].name;
const idCarte= document.createElement('p');
idCarte.innerText= `ID# ${arr[i].id}`;
const imgCarte= document.createElement('img');
imgCarte.src= arr[i].pic;
carte.appendChild(imgCarte);
carte.appendChild(txtCarte);
carte.appendChild(idCarte);
listePoke.appendChild(carte);
}
}
Here's what the code does : it gather a list of 75 pokemons from the pokeapi API. Then it fetches the data about each pokemon from the same site and stores each data into one element of the allPokemon array, when the length of this array reaches 75, we begin to create the HTML elements to display the data.
The ambigious part here is :
if(allPokemon.length === 75){
tableauFin= allPokemon.sort((a, b) => {
return a.id - b.id;
}).slice(0, 21);
createCard(tableauFin);
}
This code works but only when none of the requests fail. Otherwise, the length of the array allPokemon never reaches 75 and the rest of the code won't be executed. When I run the code, I run on XHR GET errors and the script stops before displaying the datas and it's (I think) what caused one of the promises to fail. I tried things like if(allPokemon.length === 74) (if I have one error, for example) and the code works just fine but that is surely not the solution.
Is there a way for me to "count" the errors I get from my requests so that I can do something like if(allPokemon.length === 75 - errorsCount) or maybe there is a smarter way to write my code?
Thanks in advance.
I think you can use promise all for this.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
function fetchPokemonBase(){
const promises = []
fetch("https://pokeapi.co/api/v2/pokemon?limit=75")
.then(reponse => reponse.json())
.then((allPoke) =>{
allPoke.results.forEach((pokemon) =>{
promises.push(fetchPokemonComplet(pokemon).catch(error => console.error(error)));
})
})
.then(() => {
Promise.all(promises)
.then(() => {
tableauFin= allPokemon.sort((a, b) => {
return a.id - b.id;
}).slice(0, 21);
createCard(tableauFin);
})
})
}
fetchPokemonBase();
let counter = 0
function fetchPokemonComplet(pokemon){
let objPokemonFull = {};
let url = pokemon.url;
let nameP = pokemon.name;
return fetch(url)
.then(reponse => reponse.json())
.then((pokeData) => {
counter++;
objPokemonFull.pic = pokeData.sprites.front_default;
objPokemonFull.type = pokeData.types[0].type.name;
objPokemonFull.id = pokeData.id;
return fetch(`https://pokeapi.co/api/v2/pokemon-species/${nameP}`)
.then(reponse => reponse.json())
.then((pokeData) => {
objPokemonFull.name= pokeData.names[4].name;
allPokemon.push(objPokemonFull);
})
});
}
So what you do is, instead of executing the fetch in the foreach, we push each fetch to an array of promises. Then we use the promise.all to execute them, and for each promise we catch the errors. So if one fails, the next promise will just continue.
With this code we push every fetch for the individual pokemons to an array:
promises.push(fetchPokemonComplet(pokemon).catch(error => console.error(error)));
Then we have an array of promises, which are the fetches to the server.
With the following code we execute these promises.
Promise.all(promises)
.then(() => {
tableauFin= allPokemon.sort((a, b) => {
return a.id - b.id;
}).slice(0, 21);
createCard(tableauFin);
})
the then on promise.all will be executed when all the promises are done.
And since we catch on every individual promise we don't have to worry about that.
And we don't care about the length and don't have to keep count.
Let me know if it works, or need any help.
From what I see, you will receive a list of between 0 and 75 pokemons from the first API call. Then, you fetch each one, however many it returned, up to 75 entries.
So, I think you want to just make sure your list isn't empty:
if(allPokemon.length > 0)

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.

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

How to refactor this promise fallback?

This is a classic fallback solution. If the first el does not get rendered, then it's retrying it with other renderers. How to best refactor this?
What's wrong with this code is that:
Renderers need to be in an array, but here, they're in then blocks.
They need to fall down to another renderer when the prior renderer didn't work
The first renderer is kind of the same algo like the 2th and other renderers. It checks whether the result is empty. There is a duplication there. But, the first one needs to run first, and the 2nd and others can run together, they can stop whenever one of them returns a result.
let first = $('#id1');
return this.render('priorityRenderer', first).then(isEmpty => {
let els = [
$('#id2'), $('#id3')
];
if (isEmpty) {
// put back the first el to the pool to render it again below
els.unshift(first);
}
return Promise.all(els.map(el => {
return this.render('secondaryRenderer', el)
.then(result => {
if (result) {
return result;
} else {
return this.render('3thRenderer', el)
}
})
.then(result => {
if (result) {
return result;
} else {
return this.render('4thRenderer', el)
}
})
.then(result => {
if (result) {
return result;
} else {
return this.render('5thRenderer', el)
}
});
})
});
You can use one of these approaches to call a chain of renderers until the first one returns a result:
renderers(rs, el, i=0) {
if (i < rs.length)
return this.render(rs[i], el).then(result => result || this.renderers(rs, el, i+1));
else
return Promise.reject(new Error("no renderer worked, tried: "+rs));
}
Then inside your method, you can do
let first = $('#id1');
let els = [$('#id2'), $('#id3')];
let rs = ['secondaryRenderer', '3rdRenderer', '4thRenderer', '5thRenderer'];
return Promise.all([
this.renderers(['priorityRenderer'].concat(rs), first)
].concat(els.map(el =>
this.renderers(rs, el)
)));

Categories