I am trying to get a nested Promise.all * map logic to work. I keep getting undefined values when I reach getData2.
exports.getData = (param1, param2) => {
return getData0(param1, param2)
.then(data0 => Promise.all(data0.map(e => getData1(e.id))))
.then(data1 => Promise.all(data1.map(i => getData2(i.id))))
.then(data2 => data2.map(a => getData3(a.id)))
.catch(err => console.error(err.message));
};
P.S:
1. getData0 to getData1 return structures (e.g. { a: val1, b: val2 })
2. I assume the problem is in the way getData's are written. I suspect they should return promises. Can anyone give me a dummy example about a function that returns a structure wherein both elements of the (see a and b above) are obtained in an async way?
Thanks!
Firstly, all your getData* methods should return Promise objects if they are doing any asynchronous operations. (for e.g fetching data).
getData3 could be an exception to this since it doesn't look like anything needs to be done after all getData3 calls are completed.
If that's not the case you could use similar method for getData3 as for above.
e.g data2 => Promise.all(data2.map(a => getData3(a.id)))
Now let's look over the code line by line
exports.getData = (param1, param2) => {
return getData0(param1, param2)
.then(data0 => Promise.all(data0.map(e => getData1(e.id))))
// data0 here should be an Array
.then(data1 => Promise.all(data1.map(i => getData2(i.id))))
// 1. data1 will be an Array with results from each getData1 call
// mapped by index. for e.g [result1, result2, ...]
// 2. depending on the type of result (Object/Array) from getData1
// you should extract `id` from `i`(result item) and then
// trigger `getData2`using that `id`. I'm assuming the issue is
// here.
.then(data2 => data2.map(a => getData3(a.id)))
.catch(err => console.error(err.message));
};
As for Can anyone give me a dummy example about a function that returns a structure wherein both elements of the (see a and b above) are obtained in an async way?
I believe this should answer that How to use Promise.all with an object as input
I've marked Dhruv's answer as the valid one as it explains different concepts involved in the logic I described.
getData1 and getData2 indeed make calls to an async function (in my case: doSomeEWSwork below) while getData3 is sync.
function doSomeEWSwork(param) {
var ewsFunction = '.....';
var ewsArgs = ....;
return ews.run(ewsFunction, ewsArgs)
.then(result => result.something)
.catch(err => console.log(err.message));
}
My old getData1 and getData2 used to return structure objects (e.g. {a: val1, b: val2} instead of promises (needed for Promise.all). That was causing the async blocs to never execute/evaluate.
My new getData1 and getData2 have the following skeleton:
function getData1_or_2(param) {
var promise = new Promise(function(resolve, reject) {
doSomeEWSwork(param) // <- important: settle this using 'then'
.then(res => {
var ret = { a: res.val1, b: res.val2 };
resolve(ret);
}).catch(err => {
console.log(err.message);
reject(Error(err.message));
});
});
return promise;
}
So the main method (already included in my original question) works fine now since I am returning promises for async and object for sync.
exports.getData = (param1, param2) => {
return getData0(param1, param2)
.then(data0 => Promise.all(data0.map(e => getData1(e.id))))
.then(data1 => Promise.all(data1.map(i => getData2(i.id))))
.then(data2 => data2.map(a => getData3(a.id)))
.catch(err => console.error(err.message));
};
It's just the combination of nested promises, map and structure objects that confused me.
Thanks!
Related
In node.js I am trying to get a list of bid and ask prices from a trade exchange website (within an async function). Within the foreach statement I can console.info() the object data(on each iteration) but when I put all of this into an array and then return it to another function it passes as 'undefined'.
const symbolPrice = async() => {
let symbolObj = {}
let symbolList = []
await bookTickers((error, ticker) => {
ticker.forEach(symbol => {
if (symbol.symbol.toUpperCase().startsWith(starts.toUpperCase())) {
symbolObj = {
symbol: symbol.symbol,
bid: symbol.bidPrice,
ask: symbol.askPrice
}
console.info(symbolObj);
}
symbolList.push(symbolObj)
});
const results = Promise.all(symbolList)
return results;
});
}
const symbolPriceTest = async() => {
const res = await symbolPrice(null, 'ETH', true);
console.log(res)
}
I have tried pretty much everything I can find on the internet like different awaits/Promise.all()'s. I do admit I am not as familiar with async coding as I would like to be.
So, if the basic problem here is to call bookTickers(), retrieve the asynchronous result from that call, process it and then get that processed result as the resolved value from calling symbolPrice(), then you can do that like this:
const { promisify } = require('util');
const bookTickersP = promisify(bookTickers);
async function symbolPrice(/* declare arguments here */) {
let symbolList = [];
const ticker = await bookTickersP(/* fill in arguments here */);
for (let symbol of ticker) {
if (symbol.symbol.toUpperCase().startsWith(starts.toUpperCase())) {
symbolList.push({
symbol: symbol.symbol,
bid: symbol.bidPrice,
ask: symbol.askPrice
});
}
}
return symbolList;
}
async function symbolPriceTest() {
const res = await symbolPrice(null, 'ETH', true);
console.log(res)
}
Things to learn from your original attempt:
Only use await when you are awaiting a promise.
Only use Promise.all() when you are passing it an array of promises (or an array of a mixture of values and promises).
Don't mix plain callback asynchronous functions with promises. If you don't have a promise-returning version of your asynchronous function, then promisify it so you do (as shown in my code above with bookTickersP().
Do not guess with async and await and just throw it somewhere hoping it will do something useful. You MUST know that you're awaiting a promise that is connected to the result you're after.
Don't reuse variables in a loop.
Your original implementation of symbolPrice() had no return value at the top level of the function (the only return value was inside a callback so that just returns from the callback, not from the main function). That's why symbolPrice() didn't return anything. Now, because you were using an asynchronous callback, you couldn't actually directly return the results anyway so other things had to be redone.
Just a few thoughts on organization that might be reused in other contexts...
Promisify book tickers (with a library, or pure js using the following pattern). This is just the api made modern:
async function bookTickersP() {
return new Promise((resolve, reject) => {
bookTickers((error, ticker) => {
error ? reject(error) : resolve(ticker);
})
});
}
Use that to shape data in the way that the app needs. This is your app's async model getter:
// resolves to [{ symbol, bid, ask }, ...]
async function getSymbols() {
const result = await bookTickersP();
return result.map(({ symbol, bidPrice, askPrice }) => {
return { symbol: symbol.toUpperCase(), bid: bidPrice, ask: askPrice }
});
}
Write business logic that does things with the model, like ask about particular symbols:
async function symbolPriceTest(search) {
const prefix = search.toUpperCase();
const symbols = await getSymbols();
return symbols.filter(s => s.symbol.startsWith(prefix));
}
symbolPriceTest('ETH').then(console.log);
With my team we are trying to implement a command for a really common operation for the business logic but I'm having issues handling its implementation.
Basically:
We have to retrieve an array of objects (GET).
For each of that objects we have to retrieve (GET) another object inside its father.
For each of that sub-objects (childs) we have to check a condition and if it is the wanted condition we retrieve the child, otherwise we pass null.
Q: How do I handle multiple API calls that depends from a single API call without getting outside the CY chain?
This is my current implementation (doesn't works but kinda explains the wanted logic)
Cypress.Commands.add('myCommand', (sumCriteria: Function, anotherCriteria: Function) => {
// I only retrieve fathers with certain criteria
return cy.request('GET', fathersUrl).its('body').then(fatherObjects => {
return fatherObjects.filter(father => father.childs.length && father.childs.find(sumCriteria))
}).then(filteredFathers => {
filteredFathers.forEach(father => {
// For each father I retrieve a single child
const targetChildId = father.childs.find(sumCriteria).id;
// For each single child I retrieve its data and evaluate if it has the needed criteria
cy.request('GET', `${childsUrl}/${targetChildId}`)
.its('body')
.then(property => anotherCriteria(property))
})
});
})
Thanks in advance!
You almost have the correct pattern, but instead of returning results, put them on the queue.
Cypress does two things to make this work
in a custom command, it waits for any asynchronous commands to resolve
it returns whatever is on the queue at the last evaluation
Cypress.Commands.add('myCommand', (sumCriteria, anotherCriteria) => {
cy.request('GET', fathersUrl)
.its('body')
.then(fatherObjects => {
const filteredFathers = fatherObjects.filter(father => {
return father.childs.find(sumCriteria)
});
const results = []
filteredFathers.forEach(father => {
cy.request('GET', father) // waits for all these to resove
.its('body')
.then(property => anotherCriteria(property))
})
cy.then(() => results) // returns this last queued command
})
})
A reproducible example:
Cypress.Commands.add('myCommand', (sumCriteria, anotherCriteria) => {
const fathersUrl = 'https://jsonplaceholder.typicode.com/todos/1'
cy.request('GET', fathersUrl)
.then(() => {
// simulated url extraction
const filteredFathers = [
'https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'
]
const results = []
filteredFathers.forEach(father => {
cy.request('GET', father)
.then(res => {
results.push(res.body.id)
})
});
cy.then(() => results)
});
})
cy.myCommand()
.should('deep.eq', [2,3]) // ✅ passes
I have a use case where I have to call $http.post(request) on batches of the input data.
For this, I created an array of requests. For each of them, I need to get the response of $http.post(), append it to an existing array and pass it to a rendering function. I have to make the next call only when the previous one completes and since $http.post() returns a promise (according to this), I am trying to do this using the reduce function.
function callToHttpPost(request) {
return $http.post('someurl', request);
}
function outerFunc($scope, someId) {
let completeArray = [];
let arrayOfRequests = getRequestInBatches();
arrayOfRequests.reduce((promiseChain, currentRequest) => {
console.log(promiseChain);
return promiseChain.then((previousResponse) => {
completeArray.push.apply(completeArray, previousResponse.data);
render($scope, completeArray, someId);
return callToHttpPost(currentRequest);
});
}, Promise.resolve()).catch(e => errorHandler($scope, e, someId));
}
(I have referred MDN and this answer)
But this gives me TypeError: previousResponse is undefined. The log statement shows the first promise as resolved (since it is the initial value passed to the reduce function), but the other promises show up as rejected due to this error. How can I resolve this?
Using vanilla Javascript
If the outerFunc function can be used in an async context (which it looks like it can, given that it returns nothing and the results are passed to the render function as they are built up), you could clean that right up, paring it down to:
async function outerFunc ($scope, someId) {
const completeArray = [];
try {
for (const request of getRequestInBatches()) {
const { data } = await callToHttpPost(request);
completeArray.push(...data);
render($scope, completeArray, someId);
}
} catch (e) {
errorHandler($scope, e, someId);
}
}
The sequential nature will be enforced by the async/await keywords.
Using RxJS
If you're able to add a dependency on RxJS, your can change the function to:
import { from } from 'rxjs';
import { concatMap, scan } from 'rxjs/operators';
function outerFunc ($scope, someId) {
from(getRequestInBatches()).pipe(
concatMap(callToHttpPost),
scan((completeArray, { data }) => completeArray.concat(...data), [])
).subscribe(
completeArray => render($scope, completeArray, someId),
e => errorHandler($scope, e, someId)
);
}
which revolves around the use of Observable instead of Promise. In this version, the sequential nature is enforced by the concatMap operator, and the complete array of results is reduced and emitted while being built up by the scan operator.
The error was in passing the initial value. In the first iteration of the reduce function, Promise.resolve() returns undefined. This is what is passed as previousResponse. Passing Promise.resolve({ data: [] }) as the initialValue to the reduce function solved the issue.
arrayOfRequests.reduce((promiseChain, currentRequest) => {
console.log(promiseChain);
return promiseChain.then((previousResponse) => {
completeArray.push.apply(completeArray, previousResponse.data);
render($scope, completeArray, someId);
return callToHttpPost(currentRequest);
});
}, Promise.resolve({ data: [] }))
.then(response => {
completeArray.push.apply(completeArray, previousResponse.data);
render($scope, completeArray, someId);
displaySuccessNotification();
})
.catch(e => errorHandler($scope, e, someId));
(edited to handle the final response)
I know that promise.all() fails when even 1 of the promise is failed. I only want to try for failed promises and don't want to run promise.all() again.
Any recommendations on how I can achieve this in minimal way?
Promises are eager construct and model a value obtained asynchronously,
a Promise is produced using some kind of producer, like fetch for instance.
If you retain a reference to this producer then you can replay the nmechanism
that produced the Promise in the first place.
// producer function
function getData (arg) {
const result = new Promise();
return result.then(value => {
return { ok:true, value };
}, error => {
return {
ok: false,
value: error,
// retry is a function which calls the producer with the same arguments
retry: () => getData(arg)
};
})
}
Then if you have something like:
const data = [];
// Promise<{ok: boolean, value: any, retry?: function}>
// No promises will fail in this array
const asyncResults = data.map(getResults);
Promise.all(asyncResults)
.then((results) => {
const successes = results.filter(res => res.ok);
const retrys = results.filter(res => !res.ok).map(res => res.retry()); // retry all failed promises
})
Memory leaks, stack overflow: because I retain a reference to original arguments in order to retry and the algorithm is recursive there could be a memory leak. However the algorithm cannot "stack overflow":
getData calls do not get "deeper" over time (see retry definition)
the asyncrhonicity of the algorithm prevent this behaviour if a promise was never resolved
old data is properly discarded when accessing the results as const resultData = results.filter(res => res.ok).map(res => res.value);
However the algorithm could take a long time to settle if a promise keep on not getting resolved and prevent access to the rest of the values.
In an alternative I suggest you take a look at another async primitive, not yet part of the language (maybe some day) : Observables which are designed for this kind of tasks: lazy, retry-able async operations.
You may use async package and wrap all promise calls with closure with done argument.
Then simply resolve results.
const async = require('async');
const promiseAll = promises => {
return new Promise((resolve) => {
// preparing array of functions which has done method as callback
const parallelCalls = promises.map(promise => {
return done => {
promise
.then(result => done(null, result)
.catch(error => {
console.error(error.message);
done();
});
}
});
// calling array of functions in parallel
async.parallel(
parallelCalls,
(_, results) => resolve(results.filter(Boolean))
);
});
};
router.get('/something', async (req, res) => {
...
const results = await promiseAll(promises);
...
});
Or we can simply do Promise.all without using async package:
router.get('/something', async (req, res) => {
...
const results = (
await Promise.all(
promises.map(promise => {
return new Promise(resolve => {
promise.then(resolve).catch(e => resolve());
});
});
)
).filter(Boolean);
...
});
I am trying to figure out how implement an asynchronous way of executing after an iteration is done
Essentially I have something like this:
data.businesses.map( club => {
axios.get(`${ROOT_URL}/club/${currentEmail}/${club.id}`)
.then( ({data}) => {
placeholder.push( Object.assign({}, club, data))
})
.then( () => ..do extra stuff )
})
// when data.businesses.map is done iterating all of the elements, do something
As you can see, there is some asynchronous issue when retrieving fetching information via axios
I'm still new to the idea of Promises but I am not sure where to apply, if it applicable
You can wait for a collection of promises to resolve (or at least one to reject) using Promise.all().
To gather that collection, you can have the iterator function for .map() return each Promise by removing the braces from the arrow function.
let promisedClubs = data.businesses.map( club =>
axios.get(`${ROOT_URL}/club/${currentEmail}/${club.id}`)
.then( ({data}) => {
placeholder.push( Object.assign({}, club, data))
})
.then( () => ..do extra stuff )
);
Promise.all(promisedClubs).then(clubs => {
// when data.businesses.map is done iterating all of the elements, do something
}, failedClub => {
// error handling
});