1

I've recently discovered the promises, and they made my life much easier. But there is a specific case that I'm not being capable to handle. It is when I have to call a promise inside then().

Here is my code:

const firebaseAuth = require("firebase/auth");
const auth = firebaseAuth.getAuth();
const { User } = require('../models/User');

app.post('/create_user', (req, res) => {
     user_uid = req.body.params.uid;
     newUserEmail = req.body.params.email;
     newUserPassword = req.body.params.password;
     let user;
     firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword)
     .then((userCredential) => new Promise((resolve, reject) => {
         if(userCredential == undefined) throw Error("createUserWithEmailAndPassword failed");
         user = new User(userCredential.user.uid, req.body.params.userAttributes);
         resolve()
     }))
     .then(firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))
     .then(/*other functions using User object*/)
     .then(() => {                  // finished promise chaining
        res.status(200).send();
     })
     .catch((e) => {
        console.log(e)
        res.status(403).send();
    })
});

The problem is, the .then(firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null)) is being called before the user is initialized in user = new User(userCredential.user.uid, req.body.params.userAttributes);. Could someone please help me understand why is this happening? And in case I have to call a promise inside a then(), Do I also have to nest a .catch()? Or my single .catch() at the end of my function will be capable to handle possible errors?

Edit: User does some async tasks inside the constructor, because it has to handle the images. And I can only Initialize it, after firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword) because I have to get the generated Id in userCredential

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Cesar Lopes
  • 373
  • 1
  • 10
  • Use `.then(() => firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))`. The argument of `.then` is _evaluated_ immediately, it must evaluate to a function, which is then _executed_ after the promise is resolved. – Heiko Theißen Oct 06 '22 at 15:01
  • How will the user the initialized? It does some async tasks inside the object, because it has to handle images. @HeikoTheißen And I can only Initialize it, after `firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword)` because I have to get the generated Id in `userCredential` – Cesar Lopes Oct 06 '22 at 15:03

3 Answers3

1

I see two issues here:

  1. The most simple fix for your code is to change this line:
     .then(firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))

to

     .then(() => firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))

notice the the the arrow function wrapping the call to firebaseAuth, before your code was the equivalent of asking firebaseAuth to return a function that would handle that step of the promise chain.

  1. If the only purpose of return new Promise() is to do the validation and get the user, you can simply.
  .then((userCredential) => {
   if(userCredential == undefined) throw Error("createUserWithEmailAndPassword failed");
    return new User(userCredential.user.uid, req.body.params.userAttributes);
  })

and the user will be available in the next chain as

.then(user => {
  // do stuff with user
})
Abdellah Hariti
  • 657
  • 4
  • 8
  • Actually needs to be a promise to handle the user, because It receives an Image in base64 at `req.body.params` and It is an async task to decode it. And, btw, doing: `.then(user => { // do stuff with user })`, will I have to return the user in every then() chain? – Cesar Lopes Oct 06 '22 at 15:15
  • returning the user in every chain I made it work like you said with, `.then(user => { // do stuff with user })`. Thank you for the support. – Cesar Lopes Oct 06 '22 at 15:23
1

You can use async function and try block to await the userCredential value like this:

app.post('/create_user', async(req, res) => {
  user_uid = req.body.params.uid;
  newUserEmail = req.body.params.email;
  newUserPassword = req.body.params.password;
  let user;

  try {
    const userCredential =
      await firebaseAuth.createUserWithEmailAndPassword(
        auth,
        newUserEmail,
        newUserPassword
      );

    if (userCredential == undefined)
      return res
        .status(400)
        .send('❌ CreateUserWithEmailAndPassword failed ');

    user = new User(
      userCredential.user.uid,
      req.body.params.userAttributes
    );

    await firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null);

    return res.status(201).send('Created!! ');
  } catch (error) {
    console.log('❌ Error:', error);
    return res.status(400).json({
      message: error,
    });
  }
});
  • Thank you very much for the efford, I really appreciate, but, as I described in `.then(/*other functions using User object*/)` I have to do other async operations, using the await functions would be a possibility but the code becomes too big and verbosal :/ I'll upvote for the efford xD – Cesar Lopes Oct 06 '22 at 15:23
1

We should never find new Promise() wrapping a library that already creates promises.

Also remember that the form of a chain is....

// promiseA, promiseB & promiseC are expressions that evaluate to promises
return promiseA.then(resolutionOfPromiseA => {
  // synchronous work
  return promiseB;
}).then(resolutionOfPromiseB => {
  // synchronous work
  return promiseC;
}).catch(error => {})

In newer syntax:

async function myFunction() {
  try {
    let resolutionOfPromiseA = await promiseA;
    // synchronous work
    let resolutionOfPromiseB = await promiseB;
    // synchronous work
    return promiseC;
  } catch(error) {
  }

Sticking with the OP's older syntactic style (which is perfectly good as long as it remains consistent)

 let user;
 firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword)
 .then(userCredential => {
     // note that we don't create a promise
     if(userCredential == undefined) throw Error("createUserWithEmailAndPassword failed");
     user = new User(userCredential.user.uid, req.body.params.userAttributes);
     // note that we return this one. It's good form, even if the next then doesn't use the result
     return firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null)
  })
 .then(res => {   // this res is the result of firebaseAuth.sendPasswordResetEmail
   // user is in scope here because it's in the containing scope
   /* other functions using User object */
 })
 .then(() => {                  // finished promise chaining
    res.status(200).send();
 })
 .catch((e) => {
    console.log(e)
    res.status(403).send();
})

edit If you really wanted user in the block after sendPasswordReset, and you really didn't want to keep a user variable in the containing scope, you could say...

  // move let declaration here
  let user = new User(//...

  // string an extra return user here
  // some linters think this is a faux pas
  return firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null)
    .then(() => return user)
)}.then(user =>
danh
  • 62,181
  • 10
  • 95
  • 136
  • As user does some async tasks in the constructor, I'll return the user in the `.then(userCredential...)` but, still, best answer. – Cesar Lopes Oct 06 '22 at 15:27
  • 1
    @CesarLopes, you *can* return it (by nesting a then, some frown upon this), but note that you located the `let user` in the containing scope (wrapping both then functions) so user is available anyway to all of them. What you did is a slightly less structured, but perfectly acceptable way to share scope between thens. I described in more detail in an edit – danh Oct 06 '22 at 15:41