1

I'm new to the async/await world and trying to experiment a bit with Mongoose + MongoDB + Node.JS I have this piece of code

exports.updateBrandPreferences = async (req,res) => {
    var userID = req.body.playerID;
    var newBrands = req.body.brandList;
    console.log("Ricevuto la seguente lista: " + newBrands);
    async.each(newBrands, function(brand,error) {
        Brand.findOneAndUpdate({'name': brand},{$addToSet: {peopleInterested: userID}}, {new:true}).exec().then((results) => {
            console.log(results);
            User.findOneAndUpdate({_id: userId},{$addToSet: {interestedBrands: results._id}}, {new:true}).exec().then((risultato)=> {
                console.log(risultato);
                return risultato;
            }).catch((err) => {
                return "error";
            });
            return results;
        }).catch((err) => {
            return "error";
        });
    });

    return res.status(200).json({"message": "OK"});
};

Taking some elements from the request, my objective is to associate the specified user with a list of some brands of interest. While the first query works (so Brands do now have new users inside them to symbolize their interest), this doesn't work for users as the second query doesn't get executed.
What am I missing? Schemas are the following:
Brand :

var BrandSchema = new Schema({
  name: {type: String, required: true, unique: true},
  peopleInterested: Array,
}, {
    collection: 'brands',
    retainKeyOrder: true,
    timestamps: true,
  }).plugin(mongoosePaginate);


User:

var UserSchema = new Schema({
  isAdmin: {type: Boolean, default: false},
  name: String,
  surname: String,
  email: { type: String, lowercase: true, required: true, trim: true, unique: true, dropDubs: true },
  password: { type: String, required: true },
  salt: { type: String },
  verified: { type: Boolean, default: false },
  bio: {
    type: { type: String, enum: [0,1] }, // 0='Squadra', 1='Giocatore'
    birthday: String,
    height: Number,
    number: Number,
    role: { type: String, enum: [0,1,2,3] }, // 0='Playmaker', 1='Ala', 2='Guardia', 3='Centro'
    team: String,
    city: String,
    fiscalCode: {type: String, maxlength:16}
  },
  newsletter: {type: Boolean, default: false},
  lastCheckin: {type: mongoose.Schema.Types.ObjectId, ref: 'Checkin'},
  follows: [{type: mongoose.Schema.Types.ObjectId, ref: 'Structure'}],
  interestedBrands: Array,
  score: { type: Number, default: 0 },
  profilePicture: String,
  lastLogin: {type: Date},
  facebook: {
    id: String,
    accessToken: String,
    profileImage : String
  }
}, {
  collection: 'users',
  retainKeyOrder: true,
  timestamps: true,
}).plugin(mongoosePaginate);
Gianmarco F.
  • 780
  • 2
  • 12
  • 36
  • 1
    async + async.js is horrible. Stick to `async` and promises. – Estus Flask Nov 05 '18 at 22:47
  • why is it horrible? I'm new to the async/await world and would like to know more – Gianmarco F. Nov 05 '18 at 22:51
  • Do not use the `async.js` library, it's callback style does not go well together with promises. – Bergi Nov 05 '18 at 22:56
  • `return risultato;`, `return result;` - which one do you want? Currently both are ignored. – Bergi Nov 05 '18 at 22:58
  • I actually don't need none of these two, as the only thing I care is the final return statement. I added those two only because I've read that they were necessary for avoiding an endless wait – Gianmarco F. Nov 05 '18 at 22:59
  • Promises (async function) and async library are orthogonal, they address same problems but async lib is pretty much obsolete. – Estus Flask Nov 05 '18 at 23:16

1 Answers1

1

This is the same problem as this one or this one. async function, async library and plain promises are mixed together. Promises aren't chained correctly inside callbacks.

async function (it is syntactic sugar for promises) and async library (async.each) solve similar tasks, they shouldn't be mixed unless proven otherwise. async library was a predecessor to native promises, it is callback-based and may result in callback hell; something that promises are supposed to help with (they are callback-based but provide a useful pattern for chaining).

Promises inside async function are supposed to be processed in series with await and loop statement (for..of), in parallel with await and Promise.all.

Express is unaware of promises. As explained in this answer, all rejections inside middleware should be handled.

It should be:

exports.updateBrandPreferences = async (req, res, next) => {
  try {
    var userID = req.body.playerID;
    var newBrands = req.body.brandList;

    await Promise.all(newBrands.map(async (brand) => {
        await Brand.findOneAndUpdate({'name': brand},{$addToSet: {peopleInterested: userID}}, {new:true});

        await User.findOneAndUpdate({_id: userId},{$addToSet: {interestedBrands: results._id}}, {new:true});
    }));

    return res.status(200).json({"message": "OK"});
  } catch (err) {
    next(err);
  }
};
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • This works and solve my problem: thank you! Do you happen to know a good resource to fully understand async / await and Promises? – Gianmarco F. Nov 06 '18 at 07:06
  • You're welcome. Can't recommend anything specific, MDN reference is usually enough for me. You can search 'promise await' here and check popular questions. – Estus Flask Nov 06 '18 at 07:19