问题
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?
回答1:
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;
}
回答2:
You are not await
ing 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()
.
回答3:
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(...)
来源:https://stackoverflow.com/questions/65083511/shouldnt-await-force-execution-to-wait