Using a generator to call an API multiple times and only resolve when all requests are finished? - node.js

I'm making a simple NodeJS app and I'm refactoring it out of my callback hell.
I've realised generators could be used but I'm struggling to grasp exactly how to use them.
Here's the basic flow of my function (I'm using the request-promise module):
// Iterate through keys to get values for
Object.keys(sourceData).forEach(function(key){
makeRequest(key);
})
makeRequest is a function that basically does this (it's incomplete):
// Make Request
function makeRequest(key) {
rp(apiEndpoint)
.then((data) => {
staticDictionary[key] = data.value;
})
}
I want to synchronously make a call to the endpoint, wait until it's finished getting the data, then move on to the next key in the loop using generators.
Can someone help?

You can sequence your requests one after the other, using .reduce() and promises.
// Make Request
function makeRequest(key) {
// use the key to construct the apiEndpoint here
return rp(apiEndpoint).then((data) => {
return data.value;
});
}
Object.keys(sourceData).reduce(function(p, key){
p = p.then(function(dictionary) {
return makeRequest(key).then(function(data) {
dictionary[key] = data;
return dictionary;
});
}
}, Promise.resolve({})).then(function(dictionary) {
// dictonary contains all your keys here
});
But, since none of your requests depend upon the prior requests, you could also run them all in parallel:
// Make Request
function makeRequest(key) {
// use the key to construct the apiEndpoint here
return rp(apiEndpoint).then((data) => {
return data.value;
});
}
// Iterate through keys to get values for
var staticDictionary = {};
Promise.all(Object.keys(sourceData).map(function(key){
return makeRequest(key).then(function(data) {
staticDictionary[key] = data;
});
})).then(function() {
// staticDictionary is fully available here
});
Using the Bluebird promise library, you could use Promise.props() which takes an object with promises as values and returns an object with the same keys, but resolved values as values (you pass in a map object and get back a map object):
// Make Request
function makeRequest(key) {
// use the key to construct the apiEndpoint here
return rp(apiEndpoint).then((data) => {
return data.value;
});
}
var promiseMap = {};
Object.keys(sourceData).forEach(function(key) {
promiseMap[key] = makeRequest(key);
})
return Promise.props(promiseMap).then(function(dictionary) {
// dictionary is available here
});

You can use Array.reduce function to do so.
Object.keys(sourceData).reduce(function(results, key){
makeRequest(key)
.then(function(data) {
results[key] = data;
});
}, {});
You can take a look at Bluebird, it has a lot of features related to what you are trying to acomplish.

Checkout co
co(function*(){
// Iterate through keys to get values for
for(const key in Object.keys(sourceData)){
// yield the promise
yield makeRequest(key);
}
});
// Make Request
function makeRequest(key) {
// return the promise
return rp(apiEndpoint)
.then((data) => {
staticDictionary[key] = data.value;
});
}
A better way is figuring out how does the ES6 generator work and probably do the things by yourself. Although co is a good library. :)

Related

How to create a re-usable caching method while using node-cache

I'm using node-cache on my Node.JS & Express web service. But after a while, I've got a bunch of similar logics cluttering, like these:
let data = this.cache.get('some-key')
if (data) {
return shopInfo
} else {
data = someModel.find(id)
this.cache.set('some-key', data)
return data
}
I Googled about it and found a possible solution here. But I don't wanna pass a callback function every time. Is there a better way? Or how can I modify it to utilize async/await instead of callback function?
get(key, storeFunction) {
const value = this.cache.get(key);
if (value) {
return Promise.resolve(value);
}
return storeFunction().then((result) => {
this.cache.set(key, result);
return result;
});
}
You're going to need a callback or promise as a parameter. Here's perhaps an easier way:
Helper function:
get(key, retrieveData) {
const value = this.cache.get(key);
if (value) {
return value;
}
const data = retrieveData()
this.cache.set(key, data);
return data;
}
Then you can use:
const result = get('some-key', () => someModel.find(id))
return result
Despite using a callback, it's still clean. No need to complicate your code with promises if you don't need them.
if you dont want to past callback every time, you can do opposite - modify callback itself.
// simple memoized...
function memoized(cache, fn) {
return async key => cache.has(key) ? cache.get(key) : cache.set(key, await fn(key)).get(key);
}
// some model handler...
function fetchFunction(key) {
console.log('call fetchFunction');
return Promise.resolve(key + '_value');
}
// modify callback...
// example: model.fetchFunction = memoized(new Map(), model.fetchFunction).bind(model);
fetchFunction = memoized(new Map(), fetchFunction);
// test...
(async () => {
console.log(await fetchFunction('test'));
console.log(await fetchFunction('test'));
})()

How do I use promises and loops together without promise.defer?

I'm fairly new to promises, and I'm having a problem avoiding some of the things I see described as promise anti-patterns (like Q.defer()). I have a loop that is mostly synchronous, but may occasionally need to make an asynchronous call. The code was very simple before I added the asynchronous calls, but the changes I had to make in order to keep it working with asynchronous calls is very messy.
I would like some advice on how to refactor my code. The code is trying to take selected properties of one object and add them to another. A simplified version is as follows:
function messyFunction(user, fieldArray) {
return Promise.fcall(() => {
var deferred = Promise.defer();
if (!fieldArray) {
deferred.resolve(user);
} else {
var temp = {};
var fieldsMapped = 0;
for (var i = 0; i < fieldArray.length; i++) {
var field = fieldArray[i];
if (field === 'specialValue') {
doSomethingAsync().then((result) => {
temp.specialField = result;
fieldsMapped++;
if (fieldsMapped === fieldArray.length) {
deferred.resolve(temp);
}
});
} else {
temp[field] = user[field];
fieldsMapped++;
if (fieldsMapped === fieldArray.length) {
deferred.resolve(temp);
}
}
}
}
return deferred.promise;
});
}
Here is how I would refactor it. I'm not super familiar with q, so I just used native Promises, but the principal should be the same.
To hopefully make things more clear, I've de-generalized your code a bit to turn messyFunction into getUserFields, asynchronously calculating the age.
Instead of using a for loop and using a counter to keep track of how many fields have been collected, I put them in an array and then pass it into Promise.all. Once all of the values for the fields are collected, the then on the Promise.all promise resolves to the new object.
// obviously an age can be calculated synchronously, this is just an example
// of a function that might be asynchronous.
let getAge = (user) => {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve((new Date()).getFullYear() - user.birthYear);
}, 100);
});
};
function getUserFields(user, fieldArray) {
if (!fieldArray) {
// no field filtering/generating, just return the object as
// a resolved promise.
return Promise.resolve(user);
// Note: If you want to make sure this is copy of the data,
// not just a reference to the original object you could instead
// use Object.assign to return it:
//
// return Promise.resolve(Object.assign({}, user));
} else {
// Each time we grab a field, we are either store it in the array right away
// if it is synchronous, if it is asyncronous, create a thenable representing
// its eventual value and store that.
// This array will hold them so we can later pass them into Promise.all
let fieldData = [];
// loop over all the fields we want to collect
fieldArray.forEach(fieldName => {
// our special 'age' field doesn't exist on the object but can
// be generated using the .birthYear
if (fieldName === 'age') {
// getAge returns a promise, we attach a then to it and store
// that then in fieldData array
let ageThenable = getAge(user).then((result) => {
// returning a value inside of then will resolve this
// then to that value
return [fieldName, result];
});
// add our thenable to the promise array
fieldData.push(ageThenable);
} else {
// if we don't need to do anything asyncronous, just add the field info
// to the array
fieldData.push([fieldName, user[fieldName]]);;
}
});
// Promise.all will wait until all of the thenables to be resolved before
// firing it's then. This means if none of the fields we were looking for
// is asyncronous, it will fire right away. If some of them were
// asyncronous, it will wait until they all return a value before firing.
// We are returning the then, which will transform the collected data into
// an object.
return Promise.all(fieldData).then(fields => {
// fields is an array of 2-element arrays containing the field name
// and field value for each field we are collecting. We will loop over
// this array with reduce and transform them into an object containing
// all of the properties we collected.
let newUserObj = fields.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
// Above I am using destructuring to get the key/value if the browsers
// you are targeting don't support destructuring, you can instead just
// give it a name as an array and then get the key/value from that array:
//
// let newUserObj = fields.reduce((acc, fieldData) => {
// let key = fieldData[0],
// value = fieldData[1];
// acc[key] = value;
// return acc;
// }, {});
// return new object we created to resolve the then we returned
return newUserObj;
});
}
}
let users = [
{
name: 'Tom',
birthYear: 1986
},
{
name: 'Dick',
birthYear: 1976
},
{
name: 'Harry',
birthYear: 1997
}
];
// generate a new user object with an age field
getUserFields(users[0], ['name', 'birthYear', 'age']).then(user => {
console.dir(user);
});
// generate a new user object with just the name
getUserFields(users[1], ['name']).then(user => {
console.dir(user);
});
// just get the user info with no filtering or generated properties
getUserFields(users[2]).then(user => {
console.dir(user);
});

NodeJS constructing array from asynchronious callbacks before returning

I'm writing a function that's returning and array of values. Some of the values are calculated in a callback. But I don't know how to make the program asynchronious so all of my results are in the array, and not added after they're returned.
let array = []
for (stuff : stuffs) {
if (condition) {
array.add(stuff)
} else {
api.compute(stuff, callback(resp) {
array.add(resp.stuff)
}
}
}
res.json({ "stuff": array })
In this example the array is written to the response before the async calls have finished.
How can I make this work asynchronously?
You have to use one of the approaches:
async library
Promise.all
coroutines/generators
async/await
The most cool yet, I think, is async/await. First we modify your function, so it returns a promise:
const compute = function(stuff) {
return new Promise( (resolve, reject) => {
api.compute(stuff, callback(resp){
resolve(resp.stuff)
});
});
};
Then we modify your route with async handler:
app.get('/', async function(req, res, next) {
const array = [];
for (const stuff of stuffs) {
if (condition) {
array.add(stuff);
} else {
const stuff = await compute(stuff);
array.push(stuff);
}
}
res.json({ stuff: array });
});
Note: You might need to update node version to latest.
UPDATE:
Those who are not awared, how event loop works, execute this snippet, and finish with that:
const sleep = async function(ms) {
console.log(`Sleeping ${ms}ms`);
return new Promise( resolve => setTimeout(resolve, ms));
};
async function job() {
console.log('start');
for (let t = 0; t < 10; t++) {
await sleep(100);
}
}
job();
console.log('oops did not expect that oO');
You will be surprised.
Here is an answer without package using callbacks
Create a function that's gonna recursively treat all your stuffs.
getArray(stuffs, callback, index = 0, array = []) {
// Did we treat all stuffs?
if (stuffs.length >= index) {
return callback(array);
}
// Treat one stuff
if (condition) {
array.add(stuffs[index]);
// Call next
return getArray(stuffs, callback, index + 1, array);
}
// Get a stuff asynchronously
return api.compute(stuffs[index], (resp) => {
array.add(resp.stuff);
// Call next
return getArray(stuffs, callback, index + 1, array);
});
}
How to call it?
getArray(stuffs, (array) => {
// Here you have your array
// ...
});
EDIT: more explanation
What we want to do to transform the loop you had into a loop that handle asynchronous function call.
The purpose is that one getArray call gonna treat one index of your stuffs array.
After treating one index, the function will call itself again to treat the next index, until all get treated.
-> Treat index 0 -> Treat index 1 -> Treat index 2 -> Return all result
We are using parameters to pass the infos through the process. Index to know which array part we have to treat, and array to keep a tract of what we did calculate.
EDIT: Improvement to 100% asynchronous soluce
What we have done here it's a simple transposition of your initial for loop into an asynchronous code. it can be improved so by making it totally asynchronous, which make it better but slightly more difficult.
For example :
// Where we store the results
const array = [];
const calculationIsDone = (array) => {
// Here our calculation is done
// ---
};
// Function that's gonna aggregate the results coming asynchronously
// When we did gather all results, we call a function
const gatherCalculResult = (newResult) => {
array.push(newResult);
if (array.length === stuffs.length) {
callback(array);
}
};
// Function that makes the calculation for one stuff
const makeCalculation = (oneStuff) => {
if (condition) {
return gatherCalculResult(oneStuff);
}
// Get a stuff asynchronously
return api.compute(oneStuff, (resp) => {
gatherCalculResult(resp.stuff);
});
};
// We trigger all calculation
stuffs.forEach(x => x.makeCalculation(x));

Nodejs - Mocha, Chai multiple async testing

Complete NodeJS testing noob here. Trying to individually test functions that are called through my API (meaning, rather than make an http request to a specific endpoint, which usually invokes several functions, which in turn make requests to different third party APIs, I want to test the functions themselves separately). The way they're called is I've built a class for each data source (data source = third party API), each class contains the same functions with the same exact signatures - getData and convertData, and return a callback with the results.
I've also created a module that creates many user mocks, since each user context returns different data (meaning, a user object is fed into getData, which uses certain user properties in order to determine what data should be returned).
The way I wanted to test this was to create numerous mocks, then run the functions for each. This is what I've got so far:
// Data sources to iterate over. Each is a class instance acquired through "require".
var dataSources = [
source1,
source2,
source3,
source4
];
describe('getData', function() {
this.timeout(10000);
describe('per data source,', function() {
context('standard call', function() {
// Associative array to hold the data returned, a key for each data source.
var finalResults = {};
// Iterate over all data sources
_.forEach(dataSources, function(dataSource) {
// Generate user mocks
var users = userMocks(10);
// Iterate over all users.
_.forEach(users, function (user) {
// Call each data source with each of the users.
// Numbers of calls to make - (users * data-sources), so in this case - 10*4.
dataSource.getData(user, function (err, data) {
if (err) return done(err);
// Convert the data returned to my format
dataSource.convertData(data, function (err, processedData) {
if (err) return done(err);
// Populate finalResults with converted data from each source
if (finalResults[dataSource.sourceName]) {
finalResults[dataSource.sourceName] = finalResults[dataSource.sourceName].concat(processedData);
} else {
finalResults[dataSource.sourceName] = processedData;
}
});
});
});
});
it('should return something', function(done) {
_.forEach(finalResults.keys, function(key) {
expect(finalResults[key]).to.not.be.empty;
expect(finalResults[key].length).to.be.greaterThan(0);
});
setTimeout(function() {
done();
}, 10000);
})
});
});
});
});`
This works (or at least the test passes when the query is valid, which is what I wanted), but it's cumbersome and (so very) far from elegant or effective, specifically the usage of timeout rather than using promises, async of some sort, or maybe a different alternative I'm not yet familiar with.
Since most of the resources I found (http://alanhollis.com/node-js-testing-a-node-js-api-with-mocha-async-and-should/, https://developmentnow.com/2015/02/05/make-your-node-js-api-bulletproof-how-to-test-with-mocha-chai-and-supertest/, https://justinbellamy.com/testing-async-code-with-mocha/, just to name a few) discuss direct API testing rather than specific async functions, I would love to get some input/best practices tips from more experienced Noders.
You need to know when bunch of asynchronous operations complete. Elegant way to test that is to use promises and promise aggregation:
Promise.all([ promise1, promise2, promise3 ]).then(function(results) {
// all my promises are fulfilled here, and results is an array of results
});
Wrap your dataSources into a promises using bluebird. You don't need to modify tested code self, bluebird provides convenience method:
var Promise = require('bluebird')
var dataSources = [
source1,
source2,
source3,
source4
].map(Promise.promisifyAll);
Use newly promisified functions to create promise for each call:
context('standard call', function() {
var finalResults = {};
var promiseOfResults = datasources.map(function(dataSource) {
var users = userMocks(10);
// Promise.all will take an array of promises and return a promise that is fulfilled then all of promises are
return Promise.all( users.map(function(user) {
// *Async functions are generated by bluebird, via Promise.promisifyAll
return dataSource.getDataAsync(user)
.then(dataSource.convertDataAsync)
.then(function(processedData) {
if (finalResults[dataSource.sourceName]) {
finalResults[dataSource.sourceName] = finalResults[dataSource.sourceName].concat(processedData);
} else {
finalResults[dataSource.sourceName] = processedData;
}
});
});
});
// promiseOfResults consists now of array of agregated promises
it('should return something', function(done) {
// Promise.all agregates all od your 'datasource' promises and is fulfilled when all of them are
// You don't need the promise result here, since you agegated finalResults yourself
return Promise.all( promiseOfResults ).then(function() {
_.forEach(finalResults.keys, function(key) {
expect(finalResults[key]).to.not.be.empty;
expect(finalResults[key].length).to.be.greaterThan(0);
});
done();
});
});
Rest of your test should use same Promise.all( promiseOfResults ), unless you need new set of results.

How to make sure a variable amount of Promise returning functions is resolved?

I've got a problem structuring my async code. All database-operations are async and return Promises.
I need to find a bunch of Items in the database, change them, then save them and only after all have been saved, continue with the next step in my program flow.
How can I solve this using ES6 promises?
Here is some pseudo-code illustrating my problem:
database.find("Items").then(results => {
results.forEach(result => {
result.name = "some different name";
database.save(result) // <-- is also async as find
});
return true;
}).then(next) {
// Only start here after all database.save() have been resolved
});
Use Promise.all to wait for multiple promises - it takes an array (of variable length) of promises.
database.find("Items").then(results => {
var promises = results.map(result => {
// ^^^
result.name = "some different name";
return database.save(result);
// ^^^^^^
});
return Promise.all(promises);
}).then(saves => {
// ^^^^^ an array of the results from the save operations
…; // starts after all database.save() promises have been resolved
});
You can use Promise.all():
database.find("Items").then(results => {
return Promise.all(results.map(result => {
result.name = "some different name";
return database.save(result) // <-- is also async as find
}));
}).then(() => {
// Only start here after all database.save() have been resolved
});

Resources