mongoose sequential promises - javascript

I'm trying to do some dynamic queries sequential but for any reason, the next code doesn't fulfil that desired behaviour.
var createEvent = function (user, notification) {
var action, query;
query = { agent: notification.agent, story: notification.story, type: notification.type };
action = { agent: notification.agent, story: notification.story, type: notification.type, ts: notification.ts };
return mongoose.model('Event').findOne(query).exec()
.then(function (response) {
if (response === null) {
return mongoose.model('Event').create(action)
.then(function (response) {
return mongoose.model('User').findByIdAndUpdate(user, { $push: { notifications: { _id: response._id }}});
});
}
return mongoose.model('User').findByIdAndUpdate(user, { $push: { notifications: { _id: notification._id }}}).exec();
});
setTimeout(resolve, 3000);
};
var moveNotifications = function (users) {
var promises = [];
users.map(function (user) {
if (user.notifications.length > 0) {
user.notifications.map(function (notification) {
promises.push(createEvent(user._id, notification));
});
}
});
Promise.each(promises, function (queue_item) {
return queue_item();
});
};
Could someone help me?

As you are calling createEvent inside the nested Array#map loops, you are starting all the queries at once - what you want to do is just get an array of id and notification to later pass to createEvent in Promsise.each
Note: Not sure why you use Array#map, as you never return anything from the map callback - you're basically doing Array#forEach
var moveNotifications = function(users) {
var items = [];
users.forEach(function(user) {
if (user.notifications.length > 0) {
user.notifications.forEach(function(notification) {
items.push({id: user._id, notification: notification});
});
}
});
return Promise.each(events, function(item) {
return createEvent(item._id, item.notification);
});
}
Alternatively, using Array#concat to flatten a 2 level array that is returned by using (nested) Array#map correctly you can achieve the same result
var moveNotifications = function(users) {
return Promise.each([].concat.apply([], users.map(function(user) {
return user.notifications.map(function(notification) {
return {id: user._id, notification: notification};
});
})), function(item) {
return createEvent(item._id, item.notification);
});
}
The above is easily made even more concise using the following ES2015 syntax:
arrow functions =>
spread operator ...
shorthand Object property names {a, b, c}
Destructuring Assignment - Parameter Context Matching ({a, b, c}) =>
var moveNotifications = users =>
Promise.each([].concat(...users.map(user =>
user.notifications.map(notification => ({id: user._id, notification}))
)), ({id, notification}) => createEvent(id, notification)
);
The extreme ES2016 one liner version :p
var moveNotifications = users => Promise.each([].concat(...users.map(user => user.notifications.map(notification => ({id: user._id, notification})))), ({id, notification}) => createEvent(id, notification));

Related

How can I get a timeOut to work inside the GraphQL root object?

In my GraphQL backend, the "employees" array is fetched and returned correctly even though it has to await its data, but the "slowEmployees" which contains a timeOut returns an empty array. How can I get "slowEmployees" to wait to return its data?
const root = {
hello: () => {
return 'hello world';
},
message: () => {
return 'the message';
},
books: () => {
return ['More About Linux', 'Bash Shell Scripting'];
},
employees: async () => {
const rawEmployees = (
await axios.get(
'https://edwardtanguay.netlify.app/share/employees.json'
)
).data;
const employees = [];
rawEmployees.forEach((rawEmployee) => {
const employee = {
firstName: rawEmployee.firstName,
lastName: rawEmployee.lastName,
};
employees.push(employee);
});
return employees;
},
slowEmployees: () => {
const employees = [];
setTimeout(async () => {
const rawEmployees = (
await axios.get(
'https://edwardtanguay.netlify.app/share/employees.json'
)
).data;
rawEmployees.forEach((rawEmployee) => {
const employee = {
firstName: rawEmployee.firstName,
lastName: rawEmployee.lastName,
};
employees.push(employee);
});
}, 2000);
return employees;
},
};
You need to make setTimeout compatible with async/await - a very common problem.
If you create a "Promisified" setTimeout:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
... you can await it similarly to your regular employees endpoint:
...
slowEmployees: async () => {
// Fetch raw data
const rawEmployees = (
await axios.get(
'https://edwardtanguay.netlify.app/share/employees.json'
)
).data;
// Simulate being slow
await timeout(2000);
// Transform to required data shape and return
return rawEmployees.map((rawEmployee) => {
return {
firstName: rawEmployee.firstName,
lastName: rawEmployee.lastName,
};
});
},
Note I also changed your .forEach() + .push combination to a .map() - you're performing the transformation on each array element, with no side-effects, so it's a better semantic fit.

javascript - working with multiple promises inside loop - how to return data outside of promise?

I'm struggling to understand how I can return data from multiple promises to build up an array of data.
Is there anyway I can return the data outside of the promise to push to the data variable?
I have the following:
db_sh.find({
selector: {sh: req.params.sh_id},
fields: ['_id', 'sh_id', 'time'],
sort: ['_id']
}).then(function (result) {
let data = {};
console.log('Found: ' + result.docs.length);
if (result.docs.length > 0) {
for (var i = 0; i < result.docs.length; i++) {
let student = result.docs[i];
Promise
.all([getMealBooking(student._id), getStudentData(student._id)])
.then(function(response) {
var meal_booking_data = response[0];
var student_data = response[1];
console.log(meal_booking_data);
console.log(student_data);
})
.catch(function (err) {
return res.send(false);
});
data[student.time] = [
meal_booking_data,
student_data
]
}
}
/** Sort Data Oldest First*/
data = Object.keys(data).sort().reduce((a, c) => (a[c] = data[c], a), {});
console.log(data);
res.send(data);
});
I have two promises (getMealBooking() & getStudentData()): and I am using Promise.all() to return me the results of both of these promises. I have tried to return the data but I cannot get the results to build up the data array.
Any help to be able to build up a list of all my data would be great.
You need two Promise.alls - one to iterate over each student, and a nested one to fetch the getMealBooking and getStudentData for each student.
Put everything into an async function (that catches and sends false if needed) to make the control flow easier to understand.
const { docs } = await db_sh.find({
selector: { sh: req.params.sh_id },
fields: ['_id', 'sh_id', 'time'],
sort: ['_id']
});
if (docs.length === 0) {
// no data; stop here
res.send({});
return;
};
const data = {};
await Promise.all(
docs.map(student => (
Promise.all([getMealBooking(student._id), getStudentData(student._id)])
.then(([mealBookingData, studentData]) => {
data[student.time] = [mealBookingData, studentData];
})
))
);
const sortedData = Object.keys(data).sort().reduce((a, c) => (a[c] = data[c], a), {});
res.send(sortedData);
Another Promise.all() is needed for the loop that contains the Promise.all() you've already figured out. It's better to factor a little so you can see what's happening.
function getStudentMealAndData(student) {
return Promise
.all([getMealBooking(student._id), getStudentData(student._id)])
.then(function(response) {
var meal_booking_data = response[0];
var student_data = response[1];
console.log(meal_booking_data);
console.log(student_data);
return { student, meal_booking_data, student_data };
})
.catch(function (err) {
return res.send(false);
});
}
This simplifies the then block a bit...
}).then(function (result) {
console.log('Found: ' + result.docs.length);
let promises = []
for (var i = 0; i < result.docs.length; i++) {
let student = result.docs[i];
promises.push(getStudentMealAndData(student));
}
return Promise.all(promises);
}).then(results => {
// results are an array of [{ student, meal_booking_data, student_data }, ...]
let data = results.reduce((acc, s) => {
acc[s.student.time] = [ s.meal_booking_data, s.student_data ];
return acc;
}, {});
data = Object.keys(data).sort().reduce((a, c) => (a[c] = data[c], a), {});
console.log(data);
res.send(data);
});
let arr = [];
const datas = await Promise.all([
getMealBooking(),
getStudentData()
]);
arr.push(datas[0]); //adds getMealBooking() results
arr.push(datas[1]); // adds getStudentData() results

How to iterate with two arrays inside an array using the filter() method

How can i get it to enter .map() block?
Is it possible to do this or do i need to approach this issue in another way?
var orderCompetences = [];
var nActiveApplicants = [];
function MatchCompetences() {
var _applicantCompetenceResults = nActiveApplicants.filter(xApplicant => {
xApplicant.applicantCompetences.map(applComp =>
orderCompetence.map(orderComp => {
console.log(applComp ); //never gets called
console.log(orderComp);//never gets called
return applComp === orderComp;
}));
});
return Promise.all(_applicantCompetenceResults)
.then(resp => {
console.log(resp); // Never gets called
return 1;
})
.catch(err => {
console.log(err);
return 0;
});
}
An object inside nActiveApplicants
nApplicant = {
applicantID: "",
applicantPeriods: [],
applicantCompetences: [],
applicantFullAddress: "",
applicantDuration: ""
};
I presumed that you want some of the applicantCompetences to exist in orderCompetences, using the find method to search for a valid result, and then filtered it. I mapped the remaining records into Promises.
function MatchCompetences() {
var nActiveApplicants = [{
applicantID: "abcd",
applicantPeriods: [],
applicantCompetences: ['can read', 'can talk', 'useless mostly'],
applicantFullAddress: "",
applicantDuration: ""
},
{
applicantID: "efgh",
applicantPeriods: [],
applicantCompetences: ['can read', 'can talk', 'singer mostly', 'it-will-do'],
applicantFullAddress: "",
applicantDuration: ""
}
];
var orderCompetence = ['it-will-do'];
var _applicantCompetenceResults = nActiveApplicants.filter(xApplicant => {
return typeof xApplicant.applicantCompetences.find(elem => {
return orderCompetence.indexOf(elem) !== -1;
}) !== 'undefined'
}).map(item => {
return new Promise(resolve => {
resolve(item);
})
});
return Promise.all(_applicantCompetenceResults)
.then(resp => {
console.log('remaining applicants', resp); // Never gets called
return 1;
})
.catch(err => {
console.log(err);
return 0;
});
}
MatchCompetences()
I suspect you want something like this, although it's not clear to me why you'd wrap this in a Promise.all, since I'm pretty certain _applicantCompetenceResults won't be an array of Promises:
var orderCompetences = [];
var nActiveApplicants = [];
function MatchCompetences() {
var _applicantCompetenceResults = nActiveApplicants.filter(xApplicant =>
xApplicant.applicantCompetences.some(applComp => orderCompetences.includes(applComp))
);
return Promise.all(_applicantCompetenceResults)
.then(resp => {
console.log(resp);
return 1;
})
.catch(err => {
console.log(err);
return 0;
});
}
If this doesn't do the trick for you, you may want to try to explain what you're hoping to achieve, rather than get into the details of the code you're working with.

simulate async with array and callback in javascript

if I had an array and a callback function, how do I get the results back asynchronously? here's what I've been trying
const fakeAsync = (list = [], cb = '') => {
let map = {};
list.forEach((x) => {
map[x] = cb(x)
})
return map;
}
const list = [
'user1',
'user2',
'user3'
]
const cb = (name) => {
setTimeout(function(){ return 'good ' + name }, 3000);
}
fakeAsync(list, cb)
it prints out
=> { user1: undefined, user2: undefined, user3: undefined }
I want it to be
=> { user1: 'good user1', user2: 'good user2', user3: 'good user3' }
Calling cb will return nothing directly, so the return inside the setTimeout doesn't actually return anything to be put inside the map object.
You could use Promises to get it simulated like this:
const fakeAsync = (list = [], cb = ()=>{}) => {
Promise.all( // Wait for all Promises to settle/resolve
list.map((x) => { // Make an array of Promises from the list
return new Promise((resolve) => { // Return Promise to be settled
setTimeout(() => { // Simulated delay before resolving with a return value
return resolve(cb(x))
}, 3000)
})
})
).then((map) => { // Get all returned values from the Promises
console.log(map);
});
}
const list = [
'user1',
'user2',
'user3'
]
const cb = (name) => {
return 'good ' + name; // The actual return value
}
fakeAsync(list, cb);
Some references/documentation:
https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developers.google.com/web/fundamentals/primers/promises

Turn API call forEach Loop into Promise

I have an API call with a forEach loop which I need to be finished before another function is called. It looks like this:
var getTypes = function() {
var stations = [];
stationservice.getCount('/stations')
.then(succCB, errorCB);
function succCB(data) {
data.data.forEach(function(station) {
stations.push({
id: station._id,
})
})
};
// This should only be called once the forEach Loop is done
processStations(stations);
}
I can't find an understandable example of how I can make sure the processStations() gets called once the loop is done. How can I create a promise for this such that it does what I want to achieve?
As soon as you use promises you have to chain everything that depends on that promise (or use await and async if your environment supports it):
function getTypes() {
return stationservice.getCount('/stations')
.then(function(data) {
var stations = [];
data.data.forEach(function(station) {
stations.push({
id: station._id,
})
})
return stations;
})
.then(processStations);
}
And you should return the Promise chain from your getTypes at least if the getTypes should return something that depends on the stationservice.getCount.
Instead of the forEach you might want to use map because this is what you actually do:
function getTypes() {
return stationservice.getCount('/stations')
.then(function(data) {
return data.data.map(function(station) {
return {
id: station._id,
};
})
})
.then(processStations);
}
If you want a "modern code" answer
var getTypes = function() {
return stationservice.getCount('/stations')
.then(data => data.data.map(({_id: id}) =>({id})))
.then(processStations);
}
this is equal to
var getTypes = function getTypes() {
return stationservice.getCount('/stations').then(function (data) {
return data.data.map(function (_ref) {
return { id: _ref._id };
});
}).then(processStations);
};
Though, since the map isn't asynchronous at all
const getTypes = () => stationservice.getCount('/stations').then(data => processStations(data.data.map(({_id: id}) =>({id}))));
is just fine - and in pre modern browser
var getTypes = function getTypes() {
return stationservice.getCount('/stations').then(function (data) {
return processStations(data.data.map(function (_ref) {
return { id: _ref._id };
}));
});
};
Using Async library
async.forEach(data.data, function (item, callback){
stations.push({
id: item._id,
})
callback();
}, function(err) {
processStations(stations);
});

Categories

Resources