почему async в node.js в цикле дает ошибку - PullRequest
1 голос
/ 06 июня 2011

У меня есть эта часть кода в моем приложении.

 card.getcard(command, function(toproceed,resultscard) {
    console.log('entry other cards api result'+sys.inspect(resultscard));
    if (resultscard.length==0) {
      return proceed(false,{errno:'011','queueno' : request.queueno, message:'there is no access card for particular gib'});
    }

    for (var i=0; i<resultscard.length;i++) {
      console.log('card owner'+resultscard[i].owner);

      //checking that any users is in inside of gib
      server.wrap(function(){
        server.getchannel("channels."+request.gibid+'-I', function(err, channel) {
          if (channel.users) {
            var arr=channel.users.split(',');
            if (functions.in_array(resultscard[i].owner, arr)) {
              response.users.push(resultscard[i].owner);
            }
          }
        });
        if(i==resultscard.length-1) {
          if (response.users.length<=0) {
            //here need to send sorry event that no owner is online
            request._command='sorry';
          } else {
            request._command='knock';
          } 
          return proceed(true,response);
        }
      });

    }
  }); 

при выполнении этого выдает ошибку.

entry other cards api result[ { cardid: 16,
    cardtype: 'A',
    status: 'A',
    refername: 'rahulgib',
    refertype: 'G',
    owner: 'rahul' },
  { cardid: 27,
    cardtype: 'A',
    status: 'A',
    refername: 'rahulgib',
    refertype: 'G',
    owner: 'namita' } ]
card ownerrahul
card ownernamita

node.js:178
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
TypeError: Cannot read property 'owner' of undefined
    at Object.callback (/home/myhome directory /redisyoungib/lib/yapi.js:271:50)
    at RedisClient.return_reply (/usr/local/lib/node/.npm/redis/0.6.0/package/index.js:384:29)
    at HiredisReplyParser.<anonymous> (/usr/local/lib/node/.npm/redis/0.6.0/package/index.js:78:14)
    at HiredisReplyParser.emit (events.js:64:17)
    at HiredisReplyParser.execute (/usr/local/lib/node/.npm/redis/0.6.0/package/lib/parser/hiredis.js:35:22)
    at RedisClient.on_data (/usr/local/lib/node/.npm/redis/0.6.0/package/index.js:325:27)
    at Socket.<anonymous> (/usr/local/lib/node/.npm/redis/0.6.0/package/index.js:90:14)
    at Socket.emit (events.js:64:17)
    at Socket._onReadable (net.js:673:14)
    at IOWatcher.onReadable [as callback] (net.js:177:10)

Я не понимаю, почему он дает эту ошибку?

get card дает результат из mysql карты

Функция переноса выполнила функцию обратного вызова.

getchannel возвращает данные из redis.

Ответы [ 2 ]

3 голосов
/ 06 июня 2011

Функции, которые вы создаете и передаете в server.getchannel, являются замыканиями над переменной i (ну, во всем, что находится в области видимости, но нас интересует i). Они получают постоянную ссылку до i, а не копию ее значения на момент создания функции. Это означает, что при запуске функции используется значение current , равное i, а не значение, которое было при создании функции. В результате все этих функций будут использовать то же значение i, которое является значением на конец цикла. Так как это за пределами конца массива, resultscard[i] равен undefined, поэтому попытка прочитать свойство owner из него не удалась. (Подробнее о замыканиях: Закрытия не сложны )

Итак, вы хотите, чтобы эти функции закрывались над чем-то, что является копией значения i. Обычный способ сделать это - иметь фабричную функцию, которая их создает и которая принимает значение для использования в качестве аргумента. Функция фабрики создает функцию обратного вызова, которая закрывает аргумент, значение которого не изменяется.

Не читая его слишком внимательно, применение этого к вашему коду, вероятно, выглядит что-то примерно так:

card.getcard(command, function(toproceed,resultscard) {
    console.log('entry other cards api result'+sys.inspect(resultscard));
    if (resultscard.length==0) {
      return proceed(false,{errno:'011','queueno' : request.queueno, message:'there is no access card for particular gib'});
    }

    for (var i=0; i<resultscard.length;i++) {
      console.log('card owner'+resultscard[i].owner);

      //checking that any users is in inside of gib
      server.wrap(function(){
        server.getchannel("channels."+request.gibid+'-I', makeCallback(i));
        // Call the factory function, passing in `i` -----^
        if(i==resultscard.length-1) {
          if (response.users.length<=0) {
            //here need to send sorry event that no owner is online
            request._command='sorry';
          } else {
            request._command='knock';
          } 
          return proceed(true,response);
        }
      });

    }

    // The factory function    
    function makeCallback(index) {
      return function(err, channel) {
        if (channel.users) {
          var arr=channel.users.split(',');
          // Note we use `index` -- our argument -- not `i` below
          if (functions.in_array(resultscard[index].owner, arr)) {
            response.users.push(resultscard[index].owner);
          }
        }
      };
    }
  }); 

Теперь обратный вызов, который мы создаем в makeCallback, закрывается над аргументом index для вызова, который его создал, что больше ничего не меняет. Мы проходим i, и вот мы здесь. Это все еще замыкание над другими вещами (из-за того, где определено makeCallback), но оно использует index с ними, поэтому обрабатывает правильную запись.

1 голос
/ 07 июня 2011

Это одна из самых хитрых частей javascript scope imo.

Когда вы находитесь внутри цикла и создаете анонимные функции на основе индекса из цикла, вам нужно выполнить что-то вроде bind , curry ,или анонимные функции, выполняемые самостоятельно , чтобы убедиться, что вы вводите правильное значение.

Концепция иллюстрируется на следующем примере:

var set = [];

// Store a list of functions in an array
for (var i = 0; i<5; i++) {
   set.push(function(){
      console.log(i);
   });
}

// Pull the functions back out and execute them
for (var x = 0; x<5; x++) {
   set[x]();
}

Вывод этого:

5
5
5
5
5

Ожидается?Нет. Вы ожидаете 0, 1, 2, 3, 4

Это потому, что переменные, основанные на индексе для внешней области видимости (вне созданной вами функции), не копируются, их вычисляют, когдафункция выполняется (через некоторое время, после того, как цикл уже завершен).

Чтобы получить желаемый эффект, вы можете сделать любое из того, что я упомянул выше.Это (возможно, самое простое) - самоисполняющаяся анонимная функция :

var set = [];

// Store a list of functions in an array
for (var i = 0; i<5; i++) {
   (function(i){
      set.push(function(){
         console.log(i);
      });
   })(i);
}

// Pull the functions back out and execute them
for (var x = 0; x<5; x++) {
   set[x]();
}

Это дает вам желаемый результат 0, 1, 2, 3, 4, потому что мыустановил новую область видимости, создав новую функцию, передал интересующую нас переменную (i) и немедленно выполнил функцию с нужными параметрами.Он принимает базовую форму (function (a) {}) (a).

Не зная деталей вашего кода за пределами этого блока, вы можете сделать что-то вроде этого:

card.getcard(command, function(toproceed,resultscard) {
    console.log('entry other cards api result'+sys.inspect(resultscard));
    if (resultscard.length==0) {
      return proceed(false,{errno:'011','queueno' : request.queueno, message:'there is no access card for particular gib'});
    }

    for (var i=0; i<resultscard.length;i++) {
      (function(resultscard, i){
          console.log('card owner'+resultscard[i].owner);

          //checking that any users is in inside of gib
          server.wrap(function(){
            server.getchannel("channels."+request.gibid+'-I', function(err, channel) {
              if (channel.users) {
                var arr=channel.users.split(',');
                if (functions.in_array(resultscard[i].owner, arr)) {
                  response.users.push(resultscard[i].owner);
                }
              }
            });
            if(i==resultscard.length-1) {
              if (response.users.length<=0) {
                //here need to send sorry event that no owner is online
                request._command='sorry';
              } else {
                request._command='knock';
              } 
              return proceed(true,response);
            }
          });
      })(resultscard, i);
    }
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...