Cesar Lopes
Cesar Lopes

Reputation: 413

javascript promises inside then

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

Upvotes: 1

Views: 613

Answers (3)

danh
danh

Reputation: 62676

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 =>

Upvotes: 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,
    });
  }
});

Upvotes: 1

Abdellah Hariti
Abdellah Hariti

Reputation: 707

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
})

Upvotes: 1

Related Questions