I've had a problem trying to wrap my mind around JavaScript promises. Here is my lately frustration:
var rp = require('request-promise');
function process1000() {
//gets 1000 objects from DB to process
get1000objects()
.then(function (docs) {
Promise.map(docs, addApiData)
})
.then(function (objects) {
console.log(objects)
})
}
function addApiData(element) {
return rp(url).then(function (res) {
resolve(res);
})
.catch(console.error);
}
When rp(url) is called in addApiData(), it doesn't wait until the function is resolved before beginning the next item in the array that I'm passing to Promise.map in process1000(). As soon as rp(url) is called in addApiData, addApiData is called on the next item in the array. The second .then() function in process1000 is then called, prematurely before everything has had a chance to resolve in Promise.map. I've tried a few different libraries to make my GET request but they're all having this same issue. What am I doing wrong here? Any help would be greatly appreciated.
I think you're looking for something more like this:
var rp = require('request-promise');
function process1000() {
//gets 1000 objects from DB to process
get1000objects()
.then(function (docs) {
return Promise.map(docs, addApiData)
})
.then(function (objects) {
console.log(objects);
})
}
function addApiData(element) {
return rp(url)
.catch(console.error);
}
The main point here is that you need to make sure you're returning the promises, or the next then won't know what to do with its value.
Also, try to avoid the Promise constructor antipattern.
In other words, if you return a promise, you don't need to return then as well.
These two are equivalent:
return rp(url).then(function(res) { return res; });
// and
return rp(url);
They both return a promise (rp(url) returns a promise, and rp(url).then( /* ... */) returns a promise).
Related
I have this little program that calculates totals by multiplying a rate and some hours.
The problem I am having is that the function getTasks() always return an empty map.
When I log the fields being entered in the map, they are not empty but they are entered after the function returns the map.
So I am a bit confused why this is happening.
function getTask(taskId) {
return rp({
uri: `${url}/tasks/${taskId}`,
auth: {
user: 'user',
password,
sendImmediately: false
},
json: true
}).then((task) => {
return task;
});
}
function getTasks(entries) {
const taskIds = [];
entries.forEach((entry) => {
const taskId = entry.day_entry.task_id;
if (!taskIds.includes(taskId)) {
taskIds.push(taskId);
}
});
const taskRateMap = new Map();
taskIds.forEach((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
console.log(taskRateMap);
}
});
});
return taskRateMap;
}
function calculateTasksTotals(id) {
return co(function* calculateTotalsGen() {
let total = 0;
const entries = yield getEntriesForProject(id);
const tasks = getTasks(entries);
entries.forEach((entry) => {
const rate = tasks.get(entry.day_entry.task_id);
total += rate * entry.day_entry.hours;
});
return total;
});
}
calculateTasksTotals(id)
There are multiple problems with your code:
First off, as soon as you have any asynchronous operation involved in a function, the result of that function is going to be asynchronous. You simply cannot return it synchronously. The async operation finishes sometime later. The function itself returns BEFORE the async operation finishes.
So, you return a promise from any function that uses an async operation and the caller uses that promise to know when things are done or to get the final result.
Your function getTask() is fine. It returns a promise. The .then() inside that function is redundant and not needed since task appears to already be the resolved value of the promise.
Your function getTasks() is trying to synchronously return taskRateMap, but as you've seen in testing, none of the promises have resolved yet so there are no values in taskRateMap yet. In my code version, I use Promise.all() internally to know when all the getTask() operations are done and I return a promise who's resolved value is the taskRateMap object.
The caller of getTasks() can then use that promise and a .then() handler to get access to the taskRateMap object.
Here's one way to implement getTasks():
function getTasks(entries) {
// get all unique task_id values from the entries array
const taskIds = Array.from(new Set(entries.map(entry => entry.day_entry.task_id)));
const taskRateMap = new Map();
// use Promise.all() to know when the whole array of promises is done
// use tasksIds.map() to build an array of promises
return Promise.all(taskIds.map(taskId => {
// make this promise be the return value inside the .map() callback
// so we will end up with an array of promises that will be passed to
// Promise.all()
return getTask(taskId).then(res => {
if (res.task.id === taskId) {
taskRateMap.set(taskId, res.task.default_hourly_rate);
}
})
})).then(() => {
// make final resolved value be the taskRateMap
return taskRateMap;
});
}
getTasks(entries).then(taskRateMap => {
// use taskRateMap here
});
You have an issue with your Promises. Try using Promise.all() providing all the promises as input, and return your map only when all the promises have been resolved.
Otherwise directly return your Promise.all() and create the map in the calling method.
So something like:
const tasksPromises = [];
taskIds.forEach((taskId) => {
tasksPromises.push(getTask(taskId));
});
return Promise.all(tasksPromises);
Then inside your calling method resolve the promises through then and you will have as parameter of the callback function an array where each element is the returned value of the corresponding promise.
I believe this happening because the taskRateMap isn't being populated before it's being returned. You might want to look into Promise.all()
and consider wrapping
promises = taskIds.map((taskId) => {
return Promise.resolve(getTask(taskId)).then((res) => {
if (res.task.id === taskId) {
return [taskId, res.task.default_hourly_rate];
console.log(taskRateMap);
}
});
return Promise.all(promises).then(v => /* taskRateMap*/)
I have a working promise chain:
function startSync(db) {
var promise = new Promise(function(resolve, reject) {
syncCats(db)
.then(syncTrees(db))
.then(syncCars(db))
...
.then(resolve());
});
return promise;
}
This works great. It performs each of those function calls, waiting for each one to complete before firing off another. Each of those functions returns a promise, like so:
function syncCaesar(db) {
var promise = new Promise(resolve, reject) {
// do aysnc db calls, and whatnot, resolving/rejecting appropriately
}
return promise;
}
I need to run this chain on a series of databases, sequentially.
I've tried other solutions here, but they would fire off syncCats() all at once.
For instance:
var promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(startSync(db));
}
Fires off syncCats(db[0]) and syncCats(db[1]) simultaneously.
Edit:
Performing startSync(dbs[0]).then(startSync(dbs[1])); acts the same.
Edit2: Also performs the same:
dbs.forEach(function(db) {
promise = promise.then(function() {
return startSync(db);
}
}
You're calling startSync and then passing its return value in to then. So naturally, if you do that twice, it's going to start the process twice in parallel.
Instead, pass in a function that doesn't call startSync until it's called:
var promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(function() { startSync(db); });
});
or with ES2015:
let promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(_ => startSync(db));
});
Separately, three things jump out about startSync:
It starts all its operations in parallel, not sequentially
It exhibits the promise creation anti-pattern. There's no reason for startSync to create a new promise; it already has a promise it can work with
It ensures that its resolution value is undefined
If you really want the operations running in parallel like that, I suggest being more explicit about it:
function startSync(db) {
return Promise.all([
syncCats(db),
syncTrees(db),
syncCars(db)
])
.then(_ => undefined); // This does #3
}
...but you could also do to avoid the anti-pattern:
// Still run in parallel!
function startSync(db) {
return syncCats(db)
.then(syncTrees(db))
.then(syncCars(db))
.then(_ => undefined); // Does #3
}
If you meant for them to be sequential, you need to pass functions, not the result of calling them:
function startSync(db) {
return syncCats(db)
.then(_ => syncTrees(db))
.then(_ => syncCars(db))
.then(_ => undefined); // Again, does #3
}
If you made syncCats, syncTrees, and syncCars resolve their promises with db, like this:
function syncCats(db) {
return startSomethingAsync().then(_ => db);
}
...then it could be:
function startSync(db) {
return syncCats(db)
.then(syncTrees)
.then(syncCars)
.then(_ => undefined); // Only here for #3
}
...since each would receive db as its first argument.
Finally, if you don't need to force undefined as the promise resolution value, I suggest dropping that part from the above. :-)
With appreciation to T.J and Jaromanda, I was able to solve it by fixing how startSync() was resolved:
function startSync(db) {
var promise = new Promise(function(resolve, reject) {
syncCats(db)
.then(syncTrees(db)) // not correct way to call, see below
.then(syncCars(db))
...
.then(function() { resolve(); });
});
return promise;
}
Now, as T.J points out, the way each of the .then's is being called is incorrect. He explains it better in his answer. I'm being saved by some misunderstood database layer queueing. syncTrees() and syncCars() should be running in parallel.
If I understood it correctly, you will be having an array of databases and you want to sync them one by one i.e. sync for dbs[0], once that is complete sync for dbs[1], and so on. If that's correct you can do something like this.
var syncMultipleDBs = (dataBases) {
var db = dataBases.shift();
if (!db) {
return;
}
startSync(db).then(syncMultipleDBs.bind(null, dataBases));
};
syncsyncMultipleDBs(dbs.slice());
I hope this will help.
I have some data in a array.
let data = ['firstData', 'secondData'];
The first object (firstData) always exists, secondData could be null. So I always use that to call my async function like:
myAsyncFunction(data[0]).then(result => {
//do something here
});
If data[1] exists, I wanna call myAsyncFunction with data[1].
My current solution is with nested promises. Like this:
myAsyncFunction(data[0]).then(result => {
return result;
}).then(result => {
if(data[1] !== null){
myAsyncFunction(data[1].then(resultTwo => {
//dostuff with data
});
}
});
I don't really like this solution, but it does work. It must be a better way tho. result is always a object. I tried Promise.all but it does not work. I think it's a problem with "race condition" of some sort (I am on thin ice here).
Is my current solution the only one?
Here is what myAsuncFunction looks like:
myAsyncFunction(personData){
return new Promise<any> ((resolve, reject) => {
//Assingen private variables
secondAsyncFunction(somePersonData).then(result =>
{
//Do some sync stuff
return someNewData; //not a promise
})
.then(thirdAsyncFunction)
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
Promise.all is designed to be used when you have a dynamic number of promises in an array, and you want to combine them into a promise which resolves when all of the promises in the array have resolved.
According to your use case, you should be able to use it like this:
data = data.filter(x => x !== null);
Promise.all(data.map(myAsyncFunction)).then((results) => {
const [resultOne, resultTwo] = results;
if (resultTwo) {
// handle resultTwo
}
// handle resultOne
});
Given that data is known statically before the promise result, you don't need to put the condition inside the then callback - you can make the whole chaining conditional:
var promise = myAsyncFunction(data[0]);
if(data[1] !== null){
promise.then(result =>
myAsyncFunction(data[1])
).then(resultTwo => {
//dostuff with data
});
}
In general, notice that nesting is not a bad thing, it is required when you want to branch control flow. Just don't forget to always return your promises from all (callback) functions.
You could just simply do:
const stuffOnlyRelatedToSecondData = result2 => /*do something*/
myAsyncFunction(data[0]).then(result => {
if (data[1] === null) {return null}
return myAsyncFunction(data[1]).then(stuffOnlyRelatedToSecondData)
}).then(/*Anything to do after [1] or [1,2]*/)
This way you can use the same chain and the two possible cases will look like:
myAsyncFunction(data[0]) --> END
myAsyncFunction(data[0]) --> myAsyncFunction(data[1]) --> END
Edit:
It seems that myAsyncFunction may have a promise leak, try using something like this instead:
const myAsyncFunction = personData => {
//Assigning private variables
return secondAsyncFunction(somePersonData).then(result => {
//Do some sync stuff
return someNewData; //not a promise
})
.then(thirdAsyncFunction)
}
We can write it this way because secondAsyncFunction kicks off a new Promise, hence we don't need another Promise wrapper around it. And the catch can be put in the parent Promise chain itself which executes myAsyncFunction
I'm trying to write a function that doesn't return it's value until a Promise inside the function has resolved. Here's a simplified example of what I'm trying to do.
'use strict';
function get(db, id) {
let p = db.command('GET', 'group:${id}');
p.then(g => {
g.memberNames = [];
g.members.forEach(id => {
User.get(db, id)
.then(profile => g.memberNames.push(profile.name))
.catch(err => reject(err));
});
return g;
});
}
It's a function that requests a group id and returns the data for that group. Along the way it's also throwing in the user's names into the data structure to display their names instead of their user id. My issue is that this is running asynchronously and will skip over the .then callbacks. By the time it gets to return g none of the callbacks have been called and g.memberNames is still empty. Is there a way to make the function wait to return g until all callbacks have been called?
I've seen a lot of stuff about await. Is this necessary here? It's highly undesired to add other libraries to my project.
Since your operation to return all the profile names is also async you should return a Promise fulfilled when all the other async operations complete (or reject when one of them is rejected) done with Promise.all
function get(db, id) {
let p = db.command('GET', 'group:${id}');
return p.then(g => {
return Promise.all(g.members.map(id => {
// NOTE: id is shadowing the outer function id parameter
return User.get(db, id).then(profile => profile.name)
})
})
}
Is there a way to make the function wait to return g until all callbacks have been called?
Yes, there is. But you should change your way of thinking from "wait until all the callbacks have been called" to "wait until all the promises are fulfilled". And indeed, the Promise.all function trivially does that - it takes an array of promises and returns a new promise that resolves with an array of the results.
In your case, it would be
function get(db, id) {
return db.command('GET', 'group:${id}').then(g => {
var promises = g.members.map(id => {
// ^^^ never use `forEach`
return User.get(db, id).then(profile => profile.name);
// ^^^^^^ a promise for the name of that profile
});
//
return Promise.all(promises).then(names => {
// ^^^^^^^^^^ fulfills with an array of the names
g.memberNames = names;
return g;
});
});
}
I have a code like this.. I want to fetch some contents and after all have loaded, do something.
So I use Promise.all and access resolved values later. but it is giving values but like Promise {' content here'}. (See console.log..)
I was gonna use Regex to extract it but then i check its type which isnt string but object with no keys? Why?
var request=require('request');
var urls=['someurl','someurl2','someurl3'];
var contents=[];
urls.forEach(function (u) {
contents.push(getContent(u) );
});
Promise.all(contents)
.then(function () {
// All should be loaded by now?
// Promises which are resolved are fulfiled, and values can be accessed later right?
contents.forEach(function (promise) {
var content = Promise.resolve(promise);
console.log(content); // Promise {'test'} ??
console.log(typeof content,Object.keys(content));
// object [] ???
});
}).
catch(function(err) {
//handle error here
});
function getContent(url) {
return new Promise ( function (resolve,reject) {
/** commented and stripped out for testing
request(url, function (err,response, data) {
if(err) {
reject(Error(err));
}
}); **/
resolve("test");
});
}
contents still only holds promises.
You can never directly extract the value of a promise; you can only consume it from a then() callback.
Instead, Promise.all() return a promise for the array of results.
Change your then() call to take that array as a callback parameter and use it directly.
First of all, you're accessing the results in the wrong way:
Promise.all(contents).then( function(data) {
// data holds an array with the return values of the promises
console.log(data);
});
Second thing: you're not creating a Promise correctly, essentially, you're never resolving them in your getContent() function, so you'll never get the data you want!
function getContent(url) {
return new Promise ( function (resolve,reject) {
request(url, function (err,response, data) {
if(err) {
// reject the promise in case of error
reject(Error(err));
} else {
// resolve the promise with the output you need
resolve(data);
}
});
When you call resolve(), the promise gets resolved and the input you give it is passed on.
When all promises you specified in Promise.all() are resolved, the callback will be executed and you'll be able to access the data returned with resolve().