Блок Catch не выполняется сразу после создания исключения в блоке try

#javascript #asynchronous #async-await #asynccallback

Вопрос:

У меня есть немного кода Javascript, который ведет себя не так, как я ожидал. Может кто-нибудь сказать мне, что здесь происходит?

Вот упрощенная версия:

     let recordsProcessed = 0
    await parser(fileBuffer,

      // Process row
      async (row: Record<string, any>) => {
        recordsProcessed  = 1
        try {
          console.log('Processing record', recordsProcessed)
          await processRow(row)
        } catch (e) {
          console.log('Failure at record', recordsProcessed)
        }
      }
    ) 

    async parser(fileBuffer: Buffer, rowCb: Function, ...) : Promise<number> {
      ... 
      return new Promise((resolve, reject) => {
        parseFile(fileBuffer, options)
          .on('error', (error:any) => reject(error))
          .on('data', async row => await rowCb(row))
          .on('end', (count: any) => resolve(count))
      })
      ...
    }
 

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

Это блок try/catch в асинхронном обратном вызове, который ведет себя не так, как я ожидал. Я использую тестовый файл с тремя строками, который будет вызывать исключение при каждом вызове processRow (). Итак, я ожидал бы вывода с консоли.журналы, которые будут:

 Processing record 1
Failure at record 1
Processing record 2
Failure at record 2
Processing record 3
Failure at record 3
 

Но вместо этого я получаю это:

 Processing record 1
Processing record 2
Processing record 3
Failure at record 3
Failure at record 3
Failure at record 3
 

Почему это происходит? Поскольку я ожидаю processRow(), разве он не должен находиться в той же области, что и блок try/catch, и, следовательно, catch() должен быть обработан сразу после того, как processRow() создаст исключение?

Ответ №1:

Если он обрабатывает несколько строк, parseFile() внутри должен быть какой-то цикл. Неясно, ваш ли это код или он взят из какой-то библиотеки, но этот цикл либо ожидает работы с асинхронными обратными вызовами, либо нет. Возможно, те options , кого не показывают, также влияют на это.

Если бы он использовал цикл с await , результат был бы таким, как вы ожидаете:

 async function thrower(i) {
  throw "throwing "   i;
}

let somevariable = 0;
async function wrapper(i) {
  try {
    somevariable  ;
    console.log("calling", i, "("   somevariable   ")");
    await thrower(i);
  } catch (x) {
    console.log("caught", x, "("   somevariable   ")");
  }
}

(async function() {
  for await (let i of [1, 2, 3])     // <-- async-aware loop
    wrapper(i);
})() 

Однако , если он не используется await , то цикл выполняется немедленно, когда wrapper() встречает свою собственную await строку:

 async function thrower(i) {
  throw "throwing "   i;
}

let somevariable = 0;
async function wrapper(i) {
  try {
    somevariable  ;
    console.log("calling", i, "("   somevariable   ")");
    await thrower(i);
  } catch (x) {
    console.log("caught", x, "("   somevariable   ")");
  }
}

(async function() {
  for (let i of [1, 2, 3])           // <-- async-unaware loop
    wrapper(i);
})() 

И если это древний forEach() , то это не имеет значения, даже если он попытается await :

 async function thrower(i) {
  throw "throwing "   i;
}

let somevariable = 0;
async function wrapper(i) {
  try {
    somevariable  ;
    console.log("calling", i, "("   somevariable   ")");
    await thrower(i);
  } catch (x) {
    console.log("caught", x, "("   somevariable   ")");
  }
}

(async function() {
  //[1, 2, 3].forEach(wrapper); // <- would be enough to produce the same output
  [1, 2, 3].forEach(async function(i){
    await wrapper(i);           // <- absolutely futile attempt to wait,
                                //    forEach just can't work asynchronously
  });
})() 

Ответ №2:

Вам нужно добавить новую переменную внутри синтаксического анализатора, чтобы получить номер текущей записи. Вы должны использовать эту переменную вместо глобальной.

 let recordsProcessed = 0
await parser(fileBuffer,
  // Process row
  async (row: Record<string, any>) => {
    recordsProcessed  = 1
    let thisRecordNum = recordsProcessed;
    try {
      console.log('Processing record', thisRecordNum)
      await processRow(row)
    } catch (e) {
      console.log('Failure at record', thisRecordNum)
    }
  }
) 
 

Комментарии:

1. Я не совсем ясно выразился в своем вопросе. Ваше изменение действительно приводит к правильному значению «Сбой при записи», но я все равно получаю три элемента журнала «Запись обработки», за которыми следуют три элемента «Сбой при записи». Мой реальный вопрос заключается в том, почему каждый журнал «Сбой при записи» не происходит непосредственно после каждого элемента журнала «Обработка записи»?

2. @user2943799 О, это потому, что функция немедленно запускается повторно после ошибки. Новый поток доберется console.log('Processing... до того, как исходный поток сможет обработать ошибку, перейдет к функции catch, а затем, наконец, войдет в систему console.log('Failure...