为什么在闭包内设置的值在闭包外不能使用? Nodejs相关疑问

为什么在闭包内设置的值在闭包外不能使用? Nodejs相关疑问

var orderList = doc.orderList;
var orderShopList = new Array();
orderList.forEach(function(oneOrder){
Shop.findOne({‘name’: oneOrder.shopName},function(err,shop){
orderShopList[orderShopList.length] = shop;
console.log(orderShopList);//有数值
})
})
console.log(orderShopList);//打印为null

求教这是为什么,orderList是一个对象数组,orderShopList最终不是应该有数值吗?


5 回复

在Node.js中,异步操作(如数据库查询)可能会导致一些看似奇怪的行为。你遇到的问题是因为findOne方法是异步执行的,这意味着它不会立即返回结果,而是会在数据准备好时通过回调函数通知你。因此,在console.log(orderShopList)执行时,findOne可能还没有完成所有查询。

让我们详细解释一下这个问题,并提供一个示例来帮助理解。

示例代码

const Shop = require('./models/Shop'); // 假设这是你的Shop模型

// 示例数据
const doc = {
    orderList: [
        { shopName: 'Shop A' },
        { shopName: 'Shop B' }
    ]
};

var orderList = doc.orderList;
var orderShopList = [];

orderList.forEach(function (oneOrder) {
    Shop.findOne({ 'name': oneOrder.shopName }, function (err, shop) {
        if (err) throw err;
        orderShopList.push(shop);
        console.log('Inside callback:', orderShopList); // 有数值
    });
});

setTimeout(() => {
    console.log('Outside callback after timeout:', orderShopList); // 可能为空或部分填充
}, 1000);

// 通常情况下,这里直接输出的orderShopList可能是空的
console.log('Directly outside callback:', orderShopList); // 可能为空

解释

  1. 异步回调

    • Shop.findOne 是一个异步操作,它通过回调函数传递结果。
    • 在回调函数内部,orderShopList 被更新并打印出正确的值。
    • 但是,在回调函数外部,由于异步特性,orderShopList 可能还没有被更新。
  2. 定时器

    • 使用 setTimeout 模拟等待一段时间,确保所有异步操作完成后再打印 orderShopList
    • 这样可以确保 orderShopList 已经被完全填充。
  3. 直接打印

    • 如果在所有异步操作完成之前直接打印 orderShopList,它可能仍然是空的,因为异步操作尚未完成。

如何解决

为了正确处理异步操作的结果,你可以考虑以下几种方法:

  • 使用Promise或async/await:这可以使代码更易读且更容易管理异步流程。
const asyncFunction = async () => {
    for (let oneOrder of orderList) {
        let shop = await Shop.findOne({ 'name': oneOrder.shopName });
        orderShopList.push(shop);
    }
    console.log('After all async operations:', orderShopList);
};

asyncFunction().catch(console.error);

通过这些方法,你可以更好地控制异步操作的顺序,从而避免上述问题。


这个是异步的,console.log 在 orderShopList 赋值前就被调用了。console.log值难道不应该是 "[]"么?

是哦,一时没想到,谢谢

那如果我希望console.log 在 orderShopList 赋值之后被调用需要怎么写?

在你的代码中,orderShopListforEach 循环内的回调函数中被修改,但这些修改不会立即反映到循环外部,因为回调函数可能在异步操作(如数据库查询)完成之前就结束了。因此,在 console.log(orderShopList) 时,数据库查询尚未完成,导致 orderShopList 仍然是空的。

为了更好地理解这个问题,我们来看一个简化版的示例:

var orderList = [{shopName: 'Shop1'}, {shopName: 'Shop2'}];
var orderShopList = [];

orderList.forEach(function (oneOrder) {
    setTimeout(function () {
        orderShopList.push(oneOrder.shopName);
        console.log('Inside timeout:', orderShopList); // 会打印出更新后的数组
    }, 1000);
});

console.log('Outside timeout:', orderShopList); // 打印为空数组 []

在这个例子中,setTimeout 模拟了一个异步操作。当外部的 console.log 执行时,内部的 setTimeout 还没有完成,所以 orderShopList 仍然为空。

解决这个问题的一种方法是使用 Promiseasync/await 来确保所有异步操作都已完成后再处理结果。例如:

const orderList = [{shopName: 'Shop1'}, {shopName: 'Shop2'}];
let orderShopList = [];

const promises = orderList.map(async function (oneOrder) {
    const shop = await Shop.findOne({ name: oneOrder.shopName });
    orderShopList.push(shop);
});

await Promise.all(promises);

console.log('Final orderShopList:', orderShopList); // 此时应包含所有数据

在这个例子中,我们使用了 Promise.allasync/await 来确保所有的数据库查询都完成后才继续执行下一步的操作。

回到顶部