Ошибка поиска таблицы node.js с использованием обещаний

Я собираюсь кое-чему здесь научиться. Я люблю учиться! Заранее благодарю за любую помощь!

Вот 30 строк кода Node.js JavaScript. Это стандартный поиск по таблице. Для простоты моя «таблица» представляет собой последовательность из 16 целых чисел от 0 до 15. Функция compareRow сообщает, имеет ли угаданная строка r номер строки ниже, равен или выше строки, соответствующей транзакции < эм>т; t требуется номер строки r = t. (Конечно, более длинный код сложнее.) Я удалил все, что мог, чтобы не загромождать вас.

Асинхронные проблемы (и некоторые из вас здесь в прошлых вопросах) побудили меня использовать промисы. Мой более длинный код требует bluebird и дает идентичные результаты. Этот код использует обещания JavaScript.

'use strict'
function compareRow(r, t, low, high) {
  return new Promise(function(resolve, reject) {
    console.log('compareRow   r=' + r + '   t=' + t + '   low=' + low +
        '   high=', high)
    return resolve(r - t)
  })
}
function findRow(t) {
  let lo = 0, hi = 15, mid
  return new Promise(function(resolve, reject) {
    let mustBreak = false
    let count = 0, maxCount = 3
    do {
      mid = Math.floor((lo + hi) / 2)
      compareRow(mid, t, lo, hi)
      .then(function(ans, error) {
        console.log('findRow got compareRow(' + mid + ')=' + ans)
        if (error) { mustBreak = true; return reject(error) }
        else if (ans === 0) { mustBreak = true; return resolve(mid) }
        else if (ans < 0) lo = mid
        else if (ans > 0) hi = mid
      })
    } while (!mustBreak && ++count < maxCount)
  })
}
findRow(2)
.then(function(ans1, err1) {
  console.log('for findRow(2):   ans=' + ans1 + '   err=' + err1)
})

Самокритика:

  • Использование переменной mustBreak неприемлемо. В каждом месте, где я использовал mustBreak=true, я бы предпочел и break, и вернуть разрешение(n) (или что-то еще).
  • Я понимаю, что в функциях обратного вызова JavaScript обрабатывает мои обещания, возвращаемые (например, return resolve(1)), как возвраты из функций обратного вызова, а не из функций обещаний в верхней части функции. Следовательно, при каждом таком вызове JavaScript отслеживает две цепочки выполнения: одну для resolve или reject и одну для кода, указанного после обратного вызова. Ик. Второй — непреднамеренная цепочка исполнения.
  • JSHint не любит функцию обратного вызова, включающую ans. Он правильно распознает, что функция находится внутри цикла for. Ick, за исключением того, что этот хороший совет в JSHint может не лучшим образом учитывать функции обратного вызова.

Основываясь на сообщениях console.log, которые я с тех пор удалил, выполнение выполняется так, как я хотел, до строки 17 (compareRow(mid, t, lo, hi)). Я ожидал, что console.log покажет мне что-то вроде:

compareRow   r=7   t=2   low=0   high= 15
findRow got compareRow(7)=5
compareRow   r=3   t=2   low=0   high= 7
findRow got compareRow(3)=1
compareRow   r=1   t=2   low=0   high= 3
findRow got compareRow(1)=-1
compareRow   r=1   t=2   low=1   high= 3
findRow got compareRow(3)=1
compareRow   r=2   t=2   low=1   high= 3
findRow got compareRow(3)=0
for findRow(2):   ans=2   err=undefined

Вместо этого я получил:

compareRow   r=7   t=2   low=0   high= 15
compareRow   r=7   t=2   low=0   high= 15
compareRow   r=7   t=2   low=0   high= 15
findRow got compareRow(7)=5
findRow got compareRow(7)=5
findRow got compareRow(7)=5

Кажется

  • Выполнение попадает в compareRow (возможно, своевременно), но не попадает вовремя в функцию обратного вызова, которая получает этот ответ, что приводит к бесконечному циклу.
  • Или, по крайней мере, этот цикл был бы бесконечным, за исключением счетчика циклов, который ограничивает цикл тремя циклами.
  • Учитывая, что цикл останавливается, похоже, что обратный вызов не случайно обрабатывает ответы (findRow получил compareRow(7)=5) и вызывается столько же раз, сколько выполнялся цикл (три раза).
  • К тому времени, когда запускается код обратного вызова, код для обработки вывода больше не прослушивается.

Вопросы:

  • Есть ли способ побудить планировщик JavaScript обрабатывать код так, как я намереваюсь? (Кажется, это совершенно стандартный вариант использования промисов!)

  • Как я могу написать этот код лучше?

РЕДАКТИРОВАТЬ: изменения формулировок и вопросов; все с тем же эффектом, что и исходный пост. Удалено много строк кода. Исправлена ​​синтаксическая ошибка.


person BaldEagle    schedule 30.11.2015    source источник
comment
Итак, ваша фактическая функция compareRow действительно асинхронна? В настоящее время ваш код выглядит так, как будто его лучше было бы написать без промисов :-)   -  person Bergi    schedule 30.11.2015
comment
Циклы do while не будут ждать промисов, которые вы создаете внутри своего тела. Вы не можете использовать такие циклы с асинхронным кодом. Вместо этого используйте рекурсивный подход, при котором вы можете вызвать следующий шаг из асинхронного обратного вызова обещания.   -  person Bergi    schedule 30.11.2015


Ответы (1)


@Bergi: Действительно молодец! Если вам небезразличны точки в Stack Overflow, опубликуйте формулировку в ответе, и я сделаю ваше решение принятым.

Я сказал в начале, что кое-чему здесь научусь. Да, действительно. Ниже приведен анти-шаблон, потому что JavaScript не прерывает циклы «для промисов, которые вы создаете внутри его тела. Вы не можете использовать такие циклы в асинхронном коде».

NOTE: Don't do this at home!
do {
  callPromiseFunction()
  .then(function(results, error) {
    // do something with results
  })
} while (/*some condition*/)

Альтернатива: «Вместо этого используйте рекурсивный подход, при котором вы можете вызвать следующий шаг из асинхронного обратного вызова обещания».

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

NOTE: This was sufficient in this code to start the promise chain.
return new Promise
.all([callNonrecursivePromiseFunc(1), callNonrecursivePromiseFunc(2)]
.then(function(result, error) {
  callRecursivePromiseFunction(result)
  .then(function(results, error) {
    // do something with results
  })
})

Вот как я воспользовался советом Берги, чтобы изменить свой код. Оно работает. Окончательно. Вау! Опять же, я удалил множество проверок ошибок и тому подобного, чтобы уменьшить объем.

'use strict'
let Promise = require('bluebird')
function compareRow(t, r, low, high) {
  return new Promise(function(resolve, reject) {
    console.log('compareRow   t=' + t + '   r=' + r + '   low=' + low +
        '   high=', high)
    let recursion = (r === null)  // distraction; skip it
    if (recursion) {
      r = Math.floor((low + high) / 2)
      if (r < t) low = r
      else if (r > t) high = r
      else if (r === t) return resolve(r)
      compareRow(t, null, low, high)  // recursion; this is the point
      .then(function(result, error) {
        return resolve(result)
      })
    } else {
      return resolve(r - t)  // distraction; skip it
    }
  })
}
function findRow(t) {
  return new Promise(function(resolve, reject) {
    let lo = 0, hi = 15
    console.log('findRow   t=' + t + '   lo=' + lo + '   hi=' + hi)
    return new Promise
    .all([compareRow(t, lo, lo, hi), compareRow(t, hi, lo, hi)])
    .then(function(result, error) {
      let ans = result[0]
      console.log('findRow got compareRow(' + lo + ')=' + ans)
      if (ans === 0) return resolve(lo)
      ans = result[1]
      console.log('findRow got compareRow(' + hi + ')=' + ans)
      if (ans === 0) return resolve(hi)
      compareRow(t, null, lo, hi)  // this is a recursive call
      .then(function(result, error) {
        return resolve(result)
      })
    })
  })
}
findRow(2)
.then(function(ans1, err1) {
  console.log('for findRow(2):   ans=' + ans1 + '   err=' + err1)
})

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

  • Обратите внимание на использование промисов bluebird. Этот код не работает с обещаниями JavaScript.
  • With the recursive approach:
    • The need for variable mustBreak went away. That was an "Ick".
    • Каждый раз, когда я вызываю «разрешить» или «отклонить», я нахожусь в той же области, что и объявления. Это был "Ик".
    • Я ни в коем случае не определяю функцию внутри цикла. JSHint счастливее. Это был "Ик".
  • Этот код имеет некоторую нежелательную сложность, поскольку я использую compareRow как для рекурсивной, так и для нерекурсивной цели. Ик. Мне не хватает элегантности здесь.
person BaldEagle    schedule 30.11.2015
comment
Теперь избегайте антипаттерна Promise конструктора, и вы получите от меня положительный отзыв :-) Подсказка: просто return compareRow(t, null, lo, hi); - person Bergi; 14.12.2015