为什么Node.js未使用await/回调时异步函数完全不执行?
这个现象和Node.js异步机制本身无关,是Mongoose查询API的特殊设计导致的普遍误解:Mongoose的查询方法调用后不会立刻执行数据库操作,属于懒执行逻辑。
Mongoose Query的执行规则
Mongoose中包括findByIdAndRemove、find、updateOne在内的所有查询类方法,调用时不会立即向MongoDB发送请求,只会在内存中生成一个封装了查询规则的Query实例——这个实例是一个thenable对象,只有在以下三种场景下才会真正触发数据库IO:
- 调用时传入了回调函数作为参数
- 手动调用了实例上的
.then()、.catch()或者.exec()方法 - 使用
await关键字等待这个实例(本质是await自动调用了实例的then方法)
你写的代码块1中,Product.findByIdAndRemove(prodId)执行完仅仅生成了一个未被触发的Query对象,没有任何异步IO任务被提交到Node.js的事件循环。这个对象后续如果没有被触发执行,会直接被JS引擎的垃圾回收机制回收,自然等再久也不会真的删除数据库中的数据。
常见认知误区
你默认“调用异步函数就会把任务丢到后台执行”的结论,只适用于调用瞬间就提交了异步任务的API,比如Node.js原生的fs模块方法、原生fetch等:这类API在你调用的那一刻,就已经把IO操作交给了系统底层/libuv线程池,哪怕你不等待返回结果,任务也会在后台跑完。
但Mongoose的查询方法完全不符合这个特征,它的懒执行设计本质是为了支持链式调用:你可以先初始化查询对象,再逐步拼接查询条件、排序规则,最后统一触发执行,要是调用方法时就直接发请求,后续拼接的条件就无法生效。
验证方式
你可以把代码块1中的查询语句改成Product.findByIdAndRemove(prodId).exec();,不添加await、也不传回调,等待片刻后查看数据库就会发现对应商品已经被删除——因为.exec()会主动触发查询执行,这时候删除请求才真正被发送给MongoDB,异步IO任务进入事件循环调度,哪怕你不处理返回结果,操作也会在后台完成。
注意:即便主动调用
.exec()触发了查询,不处理返回结果的写法也存在风险:如果查询过程中出现连接失败、权限不足、ID格式非法等错误,会触发未捕获的PromiseRejection,严重时会直接导致Node.js进程退出。同时外层的try/catch也无法捕获这类未被等待的异步错误。
内容的提问来源于stack exchange,提问作者pc_fuel

