I don't know if this is could be technically called a race condition...
What I have is a big baby, which can only perform one action at the time, but it's being called from an api endpoint; so simultaneous calls can occur
What I think I need to do is somehow make a queue of actions, return a promise to whoever created it, execute the actions synchronously, and resolve the promise with the value returned by the action
Here is the code (it's no real btw, just a snippet representing the problem):
class Baby {
constructor() {
this.current = 'A'
}
go(from, to) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (from === this.current) {
this.current = to
resolve()
} else {
reject(new Error('Unexpected state'))
}
}, 100)
})
}
// In order to perform the action successfully it has to do some steps in some exact order
async doAction() {
await this.go('A', 'B')
await this.go('B', 'C')
await this.go('C', 'A')
console.log('OK')
}
}
module.exports = new Baby()
And is called like this:
const baby = require('./baby')
for (let i = 0; i < 5; i++) {
doAction()
}
Thanks in advance!
Thanks to Bergi's tips, this is the final solution:
Basically, this keeps a chain of promises and when a new action needs to be added it's chained to the current chain of promises
const baby = {
current: 'A',
action: Promise.resolve(),
go(from, to) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (from === this.current) {
this.current = to
resolve()
} else {
reject(new Error('Unexpected state'))
}
}, 100)
})
},
doAction() {
this.action = this.action.then(async () => {
await this.go('A', 'B')
await this.go('B', 'C')
await this.go('C', 'A')
console.log('OK')
})
}
}
baby.doAction()
baby.doAction()
baby.doAction()
Related
I'm attempting to setup an async function so that my next step will not start until the function finishes.
I coded one module to connect to mongodb server, and then check to see if it's connected. These two functions work well together.
const mongoose = require('mongoose');
const mongoServer = `mongodb://127.0.0.1/my_database`;
const consoleColor = { green: '\x1b[42m%s\x1b[0m', yellow: '\x1b[43m%s\x1b[0m', red: '\x1b[41m%s\x1b[0m' }
exports.connectMongoose = () => {
mongoose.connect(mongoServer, { useNewUrlParser: true });
}
exports.checkState = () => {
const mongooseState = mongoose.STATES[mongoose.connection.readyState];
return new Promise((resolve) => {
if (mongooseState === 'connected') {
console.log(consoleColor.green, `Mongoose is ${mongooseState}.`);
resolve();
} else if (mongooseState === 'connecting') {
console.log(`Mongoose is ${mongooseState}.`);
setTimeout(() => {
this.checkState();
}, 1000);
} else {
console.log(consoleColor.red, `Mongoose is ${mongooseState}.`);
}
});
}
The next thing I tried to do was connect to the mongo db using my connectMongoose function, and then call a second function that will run my checkState function, and only perform the next function if it resolves (the if statement for the "connected" state.
const dbconfig = require('./dbconfig')
dbconfig.connectMongoose()
const testAwait = async () => {
await dbconfig.checkState();
console.log("Do this next");
}
testAwait()
The testAwait function runs, but it does not get to the console.log function which leads me to believe I'm doing something wrong when passing the resolve.
setTimeout(() => {
this.checkState();
}, 1000);
When this block is hit, the promise is never resolved. The original promise needs to resolve (as your code is currently, if the status is connecting, a new promise is created, but nothing waits for it, and the original promise never resolves). You could go with a pattern like this:
let attempts = 0;
const isConnected = async () => {
console.log("checking connection state...");
attempts++;
if (attempts >= 5) {
return true;
}
return false;
}
const wait = ms => new Promise(res => setTimeout(res, ms));
const checkState = async () => {
while (!(await isConnected())) {
await wait(1000);
}
return;
};
checkState().then(() => console.log("done"));
But to keep it more in line with what you've written, you could do:
const checkState = () => {
const mongooseState = Math.random() > 0.2 ? "connecting" : "connected";
return new Promise((resolve) => {
if (mongooseState === 'connected') {
console.log(`Mongoose is ${mongooseState}.`);
resolve();
} else if (mongooseState === 'connecting') {
console.log(`Mongoose is ${mongooseState}.`);
setTimeout(() => {
checkState().then(resolve);
}, 1000);
}
});
}
checkState().then(() => console.log("done"));
I think the issue here in the above code, you are only resolving your promise once. There is no rejection either. Thus, your code is blocked inside the promise. See the below example. You should exit the promise in any case resolve or reject.
const random = parseInt(Math.random());
const testAwait =
async() => {
await new Promise((resolve, reject) => {
if (random === 0) {
resolve(random);
} else {
reject(random);
}
});
console.log("Do this next");
}
testAwait()
I have this:
this.toggleWaiting()
this.results = await this.query(term)
this.toggleWaiting()
First a loading spinner gets triggered.
Then the query runs.
And when the query function ended the loading spinner gets closed.
But what if I want to just show the loading spinner, when the query takes maybe more then 0.5 seconds?
Is there a simple way to do this?
A way to achieve this is to pass the this.query(term) promise to a function which will handle triggering the toggleWaiting only when the query takes longer than the specified amount of time (using the timeout).
For example, the below takes a promise, a function (waitingFn) which will be called with the isWaiting status as well as a timeout which you can use to specify how long you want to wait before you show the loading spinner. Finally, when the promise has been fulfilled, we return the result:
async function handleWaiting(promise, waitingFn, timeout) {
let loadingStarted = false;
let timeoutInstance = null;
const timeoutPromise = new Promise((res) => {
timeoutInstance = setTimeout(() => {
loadingStarted = true;
waitingFn(true);
}, timeout);
return res();
});
function onFinished() {
clearTimeout(timeoutInstance);
if (loadingStarted) {
waitingFn(false);
}
}
try {
const [result] = await Promise.all([promise, timeoutPromise]);
onFinished();
return result;
} catch (ex) {
onFinished();
throw ex;
}
}
You can call the handleWaiting function like so:
const result = await handleWaiting(this.query(term), (isWaiting) => this.toggleWaiting(), 500);
As #FZs and #Bergi have pointed out (thank you both), the below is an antipattern due to using the promise constructor:
function handleWaiting(promise, waitingFn, timeout) {
return new Promise((res, rej) => {
let loadingStarted = false;
const timeoutInstance = setTimeout(() => {
loadingStarted = true;
waitingFn(true);
}, timeout);
function onFinished() {
if (loadingStarted) {
waitingFn(false);
}
clearTimeout(timeoutInstance);
}
return promise
.then((result) => {
onFinished();
res(result);
})
.catch((ex) => {
onFinished();
rej(ex);
});
});
}
Thanks to ljbc1994 I found a nice solution.
Inside my alpineJs object - I have this implementation:
{
waiting: false,
async handleWaiting(promise, timeout) {
return new Promise((res, rej) => {
let loadingStarted = false;
const timeoutInstance = setTimeout(() => {
loadingStarted = true;
this.waiting = true;
}, timeout);
const onFinished = () => {
if (loadingStarted) {
this.waiting = false;
}
clearTimeout(timeoutInstance);
}
promise
.then((result) => {
onFinished();
res(result);
})
.catch((ex) => {
onFinished();
rej(ex);
});
});
},
async searchForTerm(term) {
this.results = await this.handleWaiting(this.$wire.query(term), 500);
// do something with the results...
},
}
Pretty straightforward.
For someone who is interested in the full code - here is the commit inside the github repo:
https://github.com/MichaelBrauner/sunfire-form/commit/7c1f8270e107a97b03264f5ddc5c3c3ae6f7cfd7
I have the following code on which I am trying to block the execution of the method _saveAddress multiple time, so I made a promise for this method.
const [pressEventDisabled, setPressEventDisabled] = useState(false);
<TouchableOpacity style={style.button_container} activeOpacity={1} disabled={pressEventDisabled} onPress={async () => {setPressEventDisabled(true); await _saveAddress(); setPressEventDisabled(false);}} >
The problem is that I want to resolve the promise after the callback method it's executed. It's there any way to wait for the dispatch function to execute or to resolve the promise inside the callback method?
This is the method for saving the address:
const _saveAddress = () => new Promise(async (resolve) => {
var valid = _validate();
if (valid) {
const address = createAddressJson();
if (addressId) {
var addressIdProperty = {
id: addressId
};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, _onAddressSaveEditCallback));
} else {
dispatch(addAddress(address, _onAddressSaveEditCallback));
}
} else {
//notify
notifyMessage(strings.fill_required_inputs_validation);
resolve();
}
});
This is the callback method:
const _onAddressSaveEditCallback = async (success: boolean, apiValidations: any, address ? : Address, ) => {
if (success) {
if (typeof callback == 'function') {
callback(address);
}
await Navigation.pop(componentId);
} else {
setDataValidations(apiValidations);
}
};
Just do exactly what you say in the title. Nothing more, nothing less:
if (addressId) {
var addressIdProperty = {id: addressId};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
} else {
dispatch(addAddress(address, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
}
Of course, since you are passing async () => {} to addAddress instead of _onAddressSaveEditCallback you have to call _onAddressSaveEditCallback yourself since addAddress will be calling the async () => ...
But mixing promises and callbacks like this isn't great. It leads to weird looking and sometimes confusing code. A better solution is to promisify addAddress:
function addAddressPromise (address) {
return new Promise((resolve, reject) => {
addAddress(address, (success, validations, address) {
if (success) return resolve(address);
else reject(validations)
});
});
}
Now you can wait for addAddress:
const _saveAddress = async () => {
// Don't create new promise here, we do it in addAddress..
// ...
let result = await addAddressPromise(address);
dispatch(result);
await _onAddressSaveEditCallback();
// ...
}
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;
});
}