I have multiple loop in transaction chain code, but after loop done transaction immediately commit the chain and not continue to next query. my code is something like this:
return sm.sequelize.transaction(function (t) {
return Room.create({
room_type: req.body.room_type
},{transaction: t}).then(function(roomtype){
for (i=0;i<room.length;i++){
return Roomtype.create({
name : req.body.roomtype[i]
},{transaction: t}); //my transaction only work untill here and commit after that
}
for (i=0;i<another.length;i++){ //I need to continue my query to second loop here
return User.create({
email :req.body.email[i]
},{transaction:t});
}
}).then(function(result){
}).catch (function(err){
})
})
so how to make the transaction after loop go to next loop?
Remember that as soon as you use a return statement, the function is over. No more iterations of your for loop will run, and neither will the second loop. You need to do this without returning in the middle of your loops.
As long as the transaction order isn't important, the best way to do this is to use Promise.all, which takes an array of promises, and returns the results of all of them once they are all done. Keeping the rest of the logic you have above, it would look like this:
return Room.create({
room_type: req.body.room_type
}, { transaction: t })
.then(function (roomtype) {
const promises = []
for (let i = 0; i < room.length; i++) {
const promise = Roomtype.create({
name : req.body.roomtype[i]
}, { transaction: t })
promises.push(promise)
}
for (let i = 0; i < another.length; i++){
const promise = User.create({
email :req.body.email[i]
}, { transaction: t })
promises.push(promise)
}
return Promise.all(promises)
})
.then(function (results) {
. . .
})
I would go a step further though and replace the for loops with map. This is not strictly necessary, but will clean up your code a lot:
return Room.create({ room_type: req.body.room_type }, { transaction: t })
.then(function (roomtype) {
return Promise.all(req.body.roomtype.map(function (type) {
return Roomtype.create({ name: type }, { transaction: t })
}))
})
.then(function (roomResults) {
// Do the same thing for creating users here
})
Alternatively, if order is important, you are probably going to have to do something like a recursive function that will go through each Promise in order.
const createRooms = function (transaction, types, index = 0) {
return Roomtype.create({ name: types[index] }, { transaction })
.then(function (result) {
if (index < types.length - 1) {
return createRooms(transaction, types, index + 1)
}
return result
})
}
You would then call that function in your code:
return Room.create({ room_type: req.body.room_type }, { transaction: t })
.then(function (roomtype) {
return createRooms(t, req.body.roomtype)
})
.then(function (result) {
// Do the same thing for creating Users here
})
Related
Sorry if my title is not very explicit I dont know how to explain this properly.
I am trying to use the distinct function for my app that uses loopback 3 and mongodb. It seems to work right but my endpoint wont hit the return inside my function.
This is my code
const distinctUsers = await sellerCollection.distinct('userId',{
hostId : host.id,
eventId:{
"$ne" : eventId
}
}, async function (err, userIds) {;
if(!userIds || userIds.length ==0)
return [];
const filter = {
where:{
id: {
inq: userIds
}
}
};
console.log("should be last")
return await BPUser.find(filter);
});
console.log(distinctUsers);
console.log("wtf??");
//return [];
If I uncomment the return [] it will send the return and later it will show the should be last, so even when I dont have the return it seems to finish. It is now waiting for the response. I dont like the way my code looks so any pointer of how to make this look better I will take it.
It looks like the sellerCollection.distinct takes a callback as one of it's parameters, therefore, you cannot use async/await with a callback-style function, since it's not a promise.
I would suggest turning this call into a promise if you'd like to use async/await:
function findDistinct(hostId, eventId) {
return new Promise((resolve, reject) => {
sellerCollection.distinct(
'userId',
{ hostId, eventId: { "$ne": eventId } },
function (error, userIds) {
if (error) {
reject(error);
return;
}
if (!userIds || userIds.length === 0) {
resolve([]);
return;
}
resolve(userIds);
}
)
})
}
Then, you can use this new function with async/await like such:
async function getDistinctUsers() {
try {
const hostId = ...
const eventId = ...
const distinctUsers = await findDistinct(hostId, eventId)
if (distinctUsers.length === 0) {
return
}
const filter = {
where: {
id: { inq: userIds }
}
}
const bpUsers = await BPUser.find(filter) // assuming it's a promise
console.log(bpUsers)
} catch (error) {
// handle error
}
}
When I console.log this.state.events and this.state.meets the terminal logs that these two states are empty arrays. This suggests that this function executes before function 2 and 3 finish and that I am doing my promises wrong. I can't seem to figure out my issue though and I am very confused. I know that the setState is working as well since once the page renders I have a button whose function console.logs those states and it executes properly.
I have tried formatting my promises in various ways but I have no idea how they resolve before my functions finish.
obtainEventsAndMeetsRefs = () => {
return Promise.resolve(
this.userRef.get().then(
doc => {
this.setState(
{
eventRefs: doc.data().Events,
meetingsRef: doc.data().Meets
}
)
}
)
)
}
obtainEvents() {
var that = this
return new Promise(function (resolve, reject) {
for (i = 0; i < that.state.eventRefs.length; i++) {
docRef = that.state.eventRefs[i]._documentPath._parts[1];
firebase.firestore().collection('All Events').doc(docRef).get().then(
doc => {
that.setState({
events: update(that.state.events, {
$push: [
{
eventName: doc.data().eventName,
eventDescription: doc.data().eventDescription,
startDate: doc.data().startDate,
endDate: doc.data().endDate,
location: doc.data().location,
privacy: doc.data().privacy,
status: doc.data().status,
attending: doc.data().attending
}
]
})
})
}
)
if (i == that.state.eventRefs.length - 1) {
console.log('1')
resolve(true)
}
}
})
}
obtainMeetings() {
var that = this
return new Promise(function (resolve, reject) {
for (i = 0; i < that.state.meetingsRef.length; i++) {
userRef = that.state.meetingsRef[i]._documentPath._parts[1];
meetRef = that.state.meetingsRef[i]._documentPath._parts[3];
ref = firebase.firestore().collection('Users').doc(userRef)
.collection('Meets').doc(meetRef)
ref.get().then(
doc => {
that.setState({
meets: update(that.state.meets, {
$push: [
{
headline: doc.data().headline,
agenda: doc.data().agenda,
startTime: doc.data().startTime,
endTime: doc.data().endTime,
date: doc.data().date,
name: doc.data().name
}
]
})
})
}
)
if (i == that.state.meetingsRef.length - 1) {
console.log('2')
resolve(true)
}
}
})
}
orderByDate = () => {
console.log(this.state.events)
console.log(this.state.meets)
}
componentDidMount() {
this.obtainEventsAndMeetsRefs().then(
() => this.obtainEvents().then(
() => this.obtainMeetings().then(
() => this.orderByDate()
)
)
)
}
I expected the output to be an array filled with data that I inputted using setState, not an empty array.
The crux of the issue is that obtainMeetings() and obtainEvents() don't return promises that resolve when their asynchronous operations are done. Thus, you can't know when their work is done and thus you can't sequence them properly.
There are all sorts of things wrong in this code. To start with, you don't need to wrap existing promises in another promise (the promise construction anti-pattern) because that leads to errors (which you are making). Instead, you can just return the existing promise.
For example, change this:
obtainEventsAndMeetsRefs = () => {
return Promise.resolve(
this.userRef.get().then(
doc => {
this.setState(
{
eventRefs: doc.data().Events,
meetingsRef: doc.data().Meets
}
)
}
)
)
}
to this:
obtainEventsAndMeetsRefs = () => {
return this.userRef.get().then(doc => {
this.setState({
eventRefs: doc.data().Events,
meetingsRef: doc.data().Meets
});
});
}
Then, chain rather than nest and return a promise that is linked to the completion/error of the operations.
So change this:
componentDidMount() {
this.obtainEventsAndMeetsRefs().then(
() => this.obtainEvents().then(
() => this.obtainMeetings().then(
() => this.orderByDate()
)
)
)
}
to this:
componentDidMount() {
return this.obtainEventsAndMeetsRefs()
.then(this.obtainEvents.bind(this))
.then(this.obtainMeetings.bind(this))
.then(this.orderByDate.bind(this))
}
or if you prefer:
componentDidMount() {
return this.obtainEventsAndMeetsRefs()
.then(() => this.obtainEvents())
.then(() => this.obtainMeetings())
.then(() => this.orderByDate())
}
These chain instead of nest and they return a promise that tracks the whole chain so that caller knows when the chain completed and/or if there was an error.
Then, if you want your for loop to execute serially, you can make it async and use await on the promise inside the loop like this:
async obtainMeetings() {
for (let i = 0; i < this.state.meetingsRef.length; i++) {
let userRef = this.state.meetingsRef[i]._documentPath._parts[1];
let meetRef = this.state.meetingsRef[i]._documentPath._parts[3];
let ref = firebase.firestore().collection('Users').doc(userRef)
.collection('Meets').doc(meetRef)
// make the for loop wait for this operation to complete
await ref.get().then(doc => {
this.setState({
meets: update(this.state.meets, {
$push: [{
headline: doc.data().headline,
agenda: doc.data().agenda,
startTime: doc.data().startTime,
endTime: doc.data().endTime,
date: doc.data().date,
name: doc.data().name
}]
});
});
});
// this return here isn't making a whole lot of sense
// as your for loop already stops things when this index reaches
// the end value
if (i == this.state.meetingsRef.length - 1) {
console.log('2')
return true;
}
}
// it seems like you probably want to have a return value here
return true;
}
This also looks like there's a problem with the missing declarations of i, userRef, meetRef and ref in your implementation. Those should be locally declared variables.
obtainEvents() needs the same redesign.
If you can't use async/await, then you can transpile or you'd have to design the loop to work differently (more of a manual asynchronous iteration).
If you can run all the async operations in the loop in parallel and just want to know when they are all done, you can do something like this:
obtainMeetings() {
return Promise.all(this.state.meetingsRef.map(item => {
let userRef = item._documentPath._parts[1];
let meetRef = item._documentPath._parts[3];
let ref = firebase.firestore().collection('Users').doc(userRef)
.collection('Meets').doc(meetRef)
// make the for loop wait for this operation to complete
return ref.get().then(doc => {
this.setState({
meets: update(this.state.meets, {
$push: [{
headline: doc.data().headline,
agenda: doc.data().agenda,
startTime: doc.data().startTime,
endTime: doc.data().endTime,
date: doc.data().date,
name: doc.data().name
}]
});
});
});
})).then(allResults => {
// process all results, decide what the resolved value of the promise should be here
return true;
});
}
I am using Google Cloud Function, but since it runs on a older version of Node, I can not use this answer anymore. I want a function that will batch delete all the documents in a collection and returns the data from it. This is my attempt:
function deleteCollectionAndReturnResults(db, collectionRef, batchSize) {
var query = collectionRef.limit(batchSize);
return deleteQueryBatch(db, query, batchSize, []);
}
function deleteQueryBatch(db, query, batchSize, results) {
return query.get().then(snapshot => {
if (snapshot.size == 0) return 0;
var batch = db.batch();
snapshot.docs.forEach(doc => {
if (doc.exists) {results.push(doc);}
batch.delete(doc.ref);
});
return batch.commit().then(() => snapshot.size);
}).then(function(numDeleted) {
if (numDeleted >= batchSize) {
return deleteQueryBatch(db, query, batchSize, results);
}else{
return results
}
});
}
But when I run it like this:
exports.tester = functions.firestore.document('x/{x}').onCreate(event => {
deleteCollectionAndReturnResults(db, db.collection("x"), 100).then(docs => {
console.log(docs)
})
})
This is my output:
Is there something wrong why I do get the 'function returned undefined'?
Your tester function doesn't return anything. Instead, it should return a promise that's resolved when all the work is complete. It looks like you've simply forgotten to return the promise returned by deleteCollectionAndReturnResults:
exports.tester = functions.firestore.document('x/{x}').onCreate(event => {
return deleteCollectionAndReturnResults(db, db.collection("x"), 100).then(docs => {
console.log(docs)
})
})
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);
});
Please forgive the fairly case-specific question, though I think the general end goal could be of use to other people.
Goal: Populate a MongoDB with data requested from multiple JSON API URLs.
Short question: So far I've had some success with request-promise, which uses Bluebird:
var rp = require('request-promise');
var options = {
uri: 'http://www.bbc.co.uk/programmes/b006qsq5.json',
headers: {
'User-Agent': 'Request-Promise'
},
json: true
};
rp(options)
.then(function (body) {
// Mongoose allows us query db for existing PID and upsert
var query = {pid: body.programme.pid},
update = {
name: body.programme.title,
pid: body.programme.pid,
desc: body.programme.short_synopsis
},
options = { upsert: true, new: true };
// Find the document
Programme.findOneAndUpdate(query, update, options, function(err, result) {
if (err) return res.send(500, { error: err });
return res.send("succesfully saved");
});
})
.catch(function (err) {
return res.send(err);
})
But how do I loop over an array of URLs, without the program failing if any of the promises are rejected?
Something like this for example, using Bluebird, fails if any of the URLs errors.
const urls = ['http://google.be', 'http://google.uk']
Promise.map(urls, rp)
.map((htmlOnePage, index) => {
return htmlOnePage;
})
.then(console.log)
.catch((e) => console.log('We encountered an error' + e));
As I want to write to the DB with successful requests, and ignore those that might not be responding right then, I need something that skips over rejected promises, which .all does not do.
Long question:
I've been reading up about promises all day and it's making my head hurt! But I've found some good resources, such as https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html, which mentions the use of a Promise factory. Would this work for my case? I initially thought I should make each request, process the result and add it to the DB, then move on to the next request; but having seen .all I thought I should do all the requests, save the results in an array and loop over that with my DB saving function.
Should I even be using Promises for this? Maybe I should just make use of something like async.js and run my requests in series.
Thanks very much for any help or ideas.
But how do I loop over an array of URLs, without the program failing if any of the promises are rejected?
if you return a value from .catch other than a rejected promise, you will return a resolved promise
So, your .then for each individual request could return an object like
{
success: true,
result: whateverTheResultIs
}
and your catch returns
{
success: false,
error: whateverTheErrorIs
}
Really you don't NEED the success property, it's a convenience though
So the code would be - assuming process(url) returns a Promise
Promise.map(urls, url =>
process(url)
.then(result => ({result, success:true}))
.catch(error => ({error, success:false}))
)
.then(results => {
let succeeded = results.filter(result => result.success).map(result => result.result);
let failed = results.filter(result => !result.success).map(result => result.error);
});
Or, in ES5
Promise.map(urls, function (url) {
return process(url).then(function (result) {
return { result: result, success: true };
}).catch(function (error) {
return { error: error, success: false };
});
}).then(function (results) {
var succeeded = results.filter(function (result) {
return result.success;
}).map(function (result) {
return result.result;
});
var failed = results.filter(function (result) {
return !result.success;
}).map(function (result) {
return result.error;
});
});
I don't know if this fit your case, but I think You can use a counter to check when all promises has returned, regardless of the fact that each one has been resolved or rejected
var heroes = [
'Superman',
'Batman',
'Spiderman',
'Capitan America',
'Ironman',
];
function getHero(hero) {
return new Promise((resolve, reject) => {
setTimeout(() => {
return Math.round(Math.random()) ? resolve(hero + ' lives') : reject(hero + ' dead');
}, Math.random() * 3000)
})
}
function checkHeroes() {
var checked = heroes.length;
heroes.forEach((hero) => {
getHero(hero)
.then((res) => {
checked --;
console.log(res);
if (!checked) done();
})
.catch((err) => {
checked --;
console.log(err);
if (!checked) done();
});
})
}
function done() {
console.log('All heroes checked');
}
checkHeroes();
I think your issue is less about the bluebird api than structuring your promise chain.
const reducePropsToRequests = (props) => Promise.resolve(Object
.keys(props)
.reduce((acc, key) => {
acc[key] = request(sources[key]);
return acc;
}, {}));
const hashToCollection = (hash) => Promise.resolve(Object
.keys(hash)
.reduce((acc, k) => {
return [...acc, {source: k, data: hash[k]}];
}, []));
const fetchFromSources = (sources) => Promise.props(sources);
const findSeveralAndUpdate = (results) => Promise
.each(results.map(obj => {
// you have access to original {a: 'site.com'}
// here, so use that 'a' prop to your advantage by abstracting out
// your db config somewhere outside your service
return Programme.findOneAndUpdate(someConfig[obj.source], obj.data);
}))
const requestFromSeveralAndUpdate = (sources) => reducePropsToRequests(sources)
.then(fetchFromSources)
.then(hashToCollection)
.then(findSeveralAndUpdate)
.catch(/* some err handler */);
requestFromSeveralAndUpdate({ a: 'site.com', b: 'site.net' });
I'd just use request and write my own promise with try catch inside that only resolves. Pseudo example below
var request = require('request')
var urls = ['http://sample1.com/json', 'http://sample2.com/json']
var processUrl = (url) => {
return new Promise((resolve,reject)=> {
var result;
try {
var myRequest = {
uri: url,
method: 'GET',
header: {...}
};
request(option, (res,body,err)=> {
if(err) {
result = err;
return;
}
result = body;
})
}
catch(e) {
result = e;
}
finally {
resolve(result)
}
})
}