1

I have JavaScript code that looks like this (using Sequelize in Node):

if (require.main == module) {
    (async () => {
        const my_var = await MyModel.createWithChildren('name', ['one', 'two']);
        console.log('before');
        console.log(await my_var.getChildren());
        console.log('after');
    })();
}

MyModel has a static method that looks like this:

static async createWithChildren(name, children) {
    const me = await Me.create({name: name});
    const child_recs = await Child.bulkCreate(children.map((child) => ({child: child})));
    child_recs.forEach(async (rec) => {
        await rec.setParent(me);
        await rec.save();
    });
    return me;
}

But when I execute this code, where every call to the Sequelize API is preceded by an await, I get the "after" log before the last child update:

before
Executing (default): SELECT `id` ... FROM `children` WHERE `parent_id` = 1;
Executing (default): UPDATE `children` SET `parent_id`=? ...
[]
after
Executing (default): UPDATE `children` SET `parent_id`=? ...

The SELECT is coming from, I think, the call the my_var.getChildren() but is logging after "before," and the empty list is because it didn't "await" for the my_var.getChildren() call? If everything were awaiting as I expect, the log would look something like this:

before
Executing (default): UPDATE `children` SET `parent_id`=? ...
Executing (default): UPDATE `children` SET `parent_id`=? ...
Executing (default): SELECT `id` ... FROM `children` WHERE `parent_id` = 1;
['one', 'two']
after

So if I'm "awaiting" every call, why am I not seeing that?

Chuck
  • 4,662
  • 2
  • 33
  • 55

3 Answers3

2

As others have pointed out, the key factor is that the async function the results of your forEach function are not being properly awaited.

The first thing to do is to switch your forEach to a map. That will make it so that operation now returns an array of promises. For debugging, it can be helpful to put that value into a variable promises.

At that point you may see that those promises need to be awaited within you createWithChildren function.

static async createWithChildren(name, children) {
    const me = await Me.create({name: name});
    const child_recs = await Child.bulkCreate(children.map((child) => ({child: child})));
    const promises = child_recs.map(async (rec) => {
        await rec.setParent(me);
        await rec.save();
    })
    await Promise.all(promises);
    return me;
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
JuanCaicedo
  • 3,132
  • 2
  • 14
  • 36
1

You are not awaiting the function you are passing to forEach.

Usually it's easiest to just switch to for...of:

for(const rec of child_recs) {
  await /* bla bla */
}

If you want to keep the functional style, switch from forEach to map and await the result with Promise.all().

Evert
  • 93,428
  • 18
  • 118
  • 189
1

Look at your code

 child_recs.forEach(async (rec) => {
        await rec.setParent(me);
        await rec.save();
    });

In fact, you run "in parallel" a number of your "(rec)=>{...}" function and, then, exit from your createWithChildren(...) without any waiting for completion operations on child_recs. So, if you really need to await the result of operations on child_recs replace forEach(...) with something like Promise.all(...)

Vasyl Moskalov
  • 4,242
  • 3
  • 20
  • 28