best way to stack function calls in componentDidMount - javascript

componentDidMount = () => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.props.syncFirebaseToStore(user);
thenCallThis()
thenCallThis2()
}
}
whats the best way to do the above? basically I want to call 3 functions but only after the previous one has finished executing?
I tried using resolve new Promise but don't think I quite have the syntax right. I would like to chain it with .then() ideally

With promises:
componentDidMount = () => {
return firebase.auth().onAuthStateChanged((user) => {
if (!user) return Promise.reject('No user!')
return this.props.syncFirebaseToStore(user)
.then(thenCallThis)
.then(thenCallThis2)
})
}
Notes:
I've assumed that this.props.syncFirebaseToStore() returns a promise.
Wrapping the arrow function body in curlies kills the implicit return.

If your functions are already returning Promise instances - it's pretty easy to chain them up and attach an error handler, something like this:
componentDidMount = () => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//Make sure that this call and the rest return Promises
this.props.syncFirebaseToStore(user)
.then(callThis1)
.then(callThis2)
.catch((err) => {console.error(err)})
}
}
Say you want to pass arguments down the chain so that previous call returns something used by the next one, then you would do something like this:
this.props.syncFirebaseToStore(user) //returns result1 when Promise resolved
.then((results1) => {return callThis1(results1)}) //consumes results1, returns results2
.then((results2) => {return callThis2(results2)})
.catch((err) => {console.error(err)})

with async await the above code could be written as such
componentDidMount() {
firebase.auth().onAuthStateChanged(async function(user) => {
if (user) {
await this.props.syncFirebaseToStore(user);
await thenCallThis()
await thenCallThis2()
}
}
}

Related

Complete one promise array first, then complete another promise array JavaScript

I have a problem with promises and have no idea how resolve this:
My idea is to have two methods that are a "dispatcher". In testFetchFaceCharacter() call all promises that I need to resolved first. Need all data from state: {Body:{}, TopA:{}, eyes:{}} When testFetchCharacter() finished, immediately start testFetchTopCharacter(), only if all previous promises executed successfully.
However, at this point (with this code) have a errors. The promises aren't executed "Synchronously". still retrieved "asynchronously". Which "should not happen". Since it "reduce" (from what I read in several articles) avoided that behavior.
const buildCharacter = (state) => {
try {
testFetchFaceCharacter(state);
testFetchTopCharacter(state);
} catch (e) {
console.error(e + "En buildCharacter");
}
const testFetchCharacter = (state) => {
const promises = [
fetchCustom(state.Body.id, state.Body.file),
fetchCustom(state.TopA.id, state.TopA.file),
fetchCustom(state.eyes.id, state.eyes.file),
fetchCustom(state.mouth.id, state.mouth.file),
fetchCustom(state.nose.id, state.nose.file),
fetchCustom(state.eyebrow.id, state.eyebrow.file),
fetchCustom(state.Clothing.id, state.Clothing.file),
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
const testFetchTopCharacter = (state) => {
const promises = [
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
Y try this:
Execute -> Body
Execute -> TopA
Execute -> [eyes, mouth, nose, Clothing, eyebrow] //No matter the order
then
Execute [beard, hat, hair, glass] //not matter the order
First of all, there is a mistake in your code. You need to understand that as soon as you called a function, you triggered a logic that does something, even if you don't listen to the promise right away, the logic is executing.
So what happened, is that you launched all actions in "parallel" when you are doing function calls in the promises array.
Solution A
You need to "postpone" the actual call of a function until the previous function was successful, you can either do it manually, e.g.
const testFetchTopCharacter = async (state) => {
await fetchCustom(state.beard.id, state.beard.file),
await fetchCustom(state.hat.id, state.hat.file),
await fetchCustom(state.hair.id, state.hair.file),
await fetchCustom(state.glass.id, state.glass.file)
}
Solution B
If you want to use reducer you need to use callback in that array, so that when promise is completed you call the next callback in the chain.
const testFetchTopCharacter = (state) => {
const promises = [
() => fetchCustom(state.beard.id, state.beard.file),
() => fetchCustom(state.hat.id, state.hat.file),
() => fetchCustom(state.hair.id, state.hair.file),
() => fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce((promise, callback) => promise.then(callback), Promise.resolve());
}
Solution C
If an order doesn't matter to you just do Promise.all
const testFetchTopCharacter = (state) => {
return Promise.all([
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
]);
}

Using useEffect with async?

I'm using this code:
useFocusEffect(
useCallback(async () => {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (user.uid) {
const dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
return () => {
off(dbRef);
};
}
}, [])
);
I'm getting this error:
An effect function must not return anything besides a function, which
is used for clean-up. It looks like you wrote 'useFocusEffect(async ()
=> ...)' or returned a Promise. Instead, write the async function inside your effect and call it immediately.
I tried to put everything inside an async function, but then the off() is not being called.
Define the dbRef variable outside the nested async function so your cleanup callback can reference it, and allow for the possibility it may not be set as of when the cleanup occurs.
Also, whenever using an async function in a place that doesn't handle the promise the function returns, ensure you don't allow the function to throw an error (return a rejected promise), since nothing will handle that rejected promise.
Also, since the component could be unmounted during the await, you need to be sure that the async function doesn't continue its logic when we know the cleanup won't happen (because it already happened), so you may want a flag for that (didCleanup in the below).
So something like this:
useFocusEffect(
useCallback(() => {
let dbRef;
let didCleanup = false;
(async() => {
try {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (!didCleanup && user.uid) {
dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
}
} catch (error) {
// ...handle/report the error...
}
})();
return () => {
didCleanup = true;
if (dbRef) {
off(dbRef);
}
};
}, [])
);

ReactJS Promise resolve() doesn't pass the value to .then() but reject() pass the value to .catch()

I am trying to make a project with ReactJS and AWS Cognito. I am using all auth functions in auth.js folder. In login screen, I am trying to get the session information from auth.js like this:
auth.js
var getSessionInfo = async () => {
await new Promise((resolve, reject) => {
const user = Pool.getCurrentUser();
if (user) {
user.getSession((err, session) => {
if(err){
reject(err)
}else{
resolve(session)
}
})
} else {
reject()
}
})
}
and in login.js
getSessionInfo()
.then(session => {
console.log("session:", session)
setIsAuth(true)
if (isAuth) {
history.push("/home")
}
})
.catch(err => {
console.log("err:", err)
})
In login.js, .then(session => {...}) this session is always undefined. None of the resolves returns the values, no matter what I write in it.
But the fun part is if I use reject() instead of resolve() and use .catch() instead of .then() the values passes perfectly. If I can't find the cause of it I might use Promises this way.
The promise await is not being returned.
Therefore, even though the value is resolved, it's not being returned to the callback.
Add return here:
return await new Promise((resolve, reject) => {
The getSessionInfo forgets to return anything from the function, so the returned value is always undefined. You also don't have to await the promise as you are not using the result of the promise later on in the function.
Instantly return the promise and lose the async / await (which would make sense when you call the getSessionInfo function) to solve your issue.
const getSessionInfo = () => new Promise((resolve, reject) => {
const user = Pool.getCurrentUser();
if (user) {
user.getSession((err, session) => {
if (err) {
reject(err)
}
resolve(session);
});
}
reject();
});
Down here async / await would make sense as you want to actually wait for the value from getSessionInfo before continuing.
(async () => {
try {
const session = await getSessionInfo();
console.log("session:", session)
setIsAuth(true)
if (isAuth) {
history.push("/home")
}
} catch(error) {
console.log("err:", err)
}
})()

setTimeout on promises inside a for loop

What I want to do is this:
Loop over a collection of data, for each data element make a call to an API, wait that the promise fail or resolve, pause for 30sec... then do this again for the next data element until there is nothing to iterate over in the collection ... finally display a 'done' message.
So far this is the code I wrote, gathering ideas in other SO questions, and this is not working the way I'd like.
populateDB();
// these 2 helper functions were found on SO
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
await timeout(30000);
return fn(...args);
}
// this is the main function that makes the api calls
function populateDB() {
for (let stock of universe.universe) {
sleep(() => {
// actual API call
return alpha.data
.daily(stock)
.then(data => {
// write data to the db when promise resolves
db.get("stocks")
.push({ [stock]: polishData(data) })
.write();
})
.catch(err => console.log(err));
});
}
console.log("Done!");
}
All the promised are still chaining one after another there is no pause. I don't think I understand Promises enough to debug this... what would be the code that works the way I'd like it to ?
Use async/await in your populateDB function:
async function populateDB() {
for (let stock of universe.universe) {
await sleep(() => {
// actual API call
return alpha.data
.daily(stock)
.then(data => {
// write data to the db when promise resolves
db.get("stocks")
.push({ [stock]: polishData(data) })
.write();
})
.catch(err => console.log(err));
});
}
console.log("Done!");
}

What's the promise chaining equivalent of awaiting multiple async functions?

I'm studying the usage of promsies and async/await.
I've wrote the following code, which does the following:
It gets some database's data (using Knex.js),
Handles that data,
Assigns the handled data into a specified property.
These 3 steps are done multiple times (In the following code, it's done twice), and are always awaited:
async function run() {
return await getData();
}
async function getData() {
let handledData = {};
handledData.res1 = await knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
handledData.res2 = await knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data, handledData))
.catch(handleError);
return handledData;
}
async function handleData(data) {
let res = [];
data.forEach(item => {
res.push(item.column1);
});
return res;
}
function handleError (error) {
console.log(error);
}
Now, I'm trying to write the promise-chaining equivalent of getData, and this is what I came up with:
async function getData() {
let handledData = {};
let promise = new Promise(function(resolve, error){ resolve(); });
promise
.then(function () {
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res1 = handled;
return knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res2 = handled;
return handledData;
})
.catch(handleError);
return promise;
}
But this doesn't quite work. What happens is that after the first then returns, the await inside run ends its awaiting, which causes run to return - and only then the second then is executed.
How can I make the promise-chaining version work as the multiple-await version does?
(and please, feel free to point out any misunderstaings I made of promises/async-await)
If possible, I'd recommend using Promise.all instead, it'll make your script run faster in addition to making the logic clearer:
const getData = Promise.all([
knex.select('column1').from('table1').where('column1', '1')
// Simply pass the function name as a parameter to the `.then`:
.then(handleData)
.catch(handleError),
knex.select('column1').from('table1').where('column1', '2')
.then(handleData)
.catch(handleError)
])
.then(([res1, res1]) => ({ res1, res2 }));
knex.select().then() returns a promise, so you don't need to wrap it in another promise you just need to set up the chain of then()s and return the whole thing. The result will be that getData returns the promise from the last then. You can return the value you want from that then() which will make it available to the caller. For example:
function run() {
getData()
.then(handledData => console.log(handledData) /* do something with data */)
}
function getData() {
let handledData = {};
// need to return this promise to callers can access it
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handledData.res1 = handleData(data))
.then(() => knex.select('column1').from('table1').where('column1', '2'))
.then(data => {
handledData.res2 = handleData(data)
return handledData
})
.catch(handleError);
}
You could also set this up to pass the handledData object thought the chain, but you don't need to in this case.
The function handleData() is synchronous, so you don't need to make it an async function.

Categories