Функции обратного вызова были очень распространены в приложениях JavaScript и Node. Из необходимости. А с функциями обратного вызова появились нечеткие потоки программ, параллельные реальности, вложенная сложность и многое другое.

ECMAScript представил промисы несколько лет назад, а затем добавил аккуратный синтаксис await и асинхронный. Это позволяет нам иметь асинхронное выполнение как часть обычного потока программы. Все аккуратно аккуратно и последовательно.

Однако все еще есть много библиотек, которые работают с функциями обратного вызова в старом стиле. Было бы неплохо, если бы мы могли каким-то образом убрать эти функции обратного вызова и сделать все асинхронным и ожидающим? Я уверен, что многие статьи уже обсуждают это, и я прочитал несколько. Однако они не были так ясны для меня, как я полагаю, что могли бы быть. Поэтому ради себя я попытался создать несколько очень простых примеров, которые помогут мне легко обращаться к функции обратного вызова в будущих обстоятельствах.

Почти несерьезный пример для начала. Здесь функция обратного вызова показана красным прямоугольником:

function callme(name, callback) { 
  const message = "Hello "+ name callback(message) 
} 
callme("Lucas"
      , (msg)=> {console.log(`Message from callme: ${msg}`)}
)

Здесь нет необходимости использовать функцию обратного вызова, так как нет асинхронной обработки. Но служит примером. Следующий фрагмент кода эквивалентен. Однако вызывающий код не передается в функцию обратного вызова: он ожидает ответа, а затем делает то, что функция обратного вызова делала до этого.

async function callmeAsync(name) { 
  const message = "Hello "+ name 
  return new Promise((resolve,reject) => {resolve(message)}) 
} 
async function becalled() { 
  const msg = await callmeAsync("Lucas") 
  console.log(`Message from callmeAsync: ${msg}`) 
} 
becalled()

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

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

Результат обеих конструкций одинаков:

Классический пример использования функции обратного вызова — setTimeout, когда выполнение функции запланировано на более поздний момент. Здесь функция обратного вызова, которая должна быть выполнена через 1500 мс, показана красным прямоугольником.

function moveOn() {
  console.log("After pausing, now move on");
}
setTimeout(function () { moveOn() }, 1500)

Используя async и await, этот код можно было бы написать следующим образом:

async function pause(time) {
  return new Promise((resolve, reject) => setTimeout( () => {return resolve() }, time)
)
}
async function doAfterPause() {
  await pause(1500)
  moveOn()
}
doAfterPause()

Опять же, логика [бывшей] функции обратного вызова показана в прямоугольнике.

Опять же, обещание используется для переноса асинхронной операции (setTimeout), а функция обратного вызова ничего не делает, кроме немедленного разрешения. В этом случае метод resolve() не создает никакого значения. Ожидание не будет продолжаться с вызовом moveOn() до тех пор, пока вызов функции pause() не создаст значение, что происходит после истечения времени таймера и разрешения промиса.

Чтение файлов

Чтение файлов в Node JS — классический пример асинхронной операции (хотя доступна блокирующая функция синхронного чтения файлов). В типичном примере чтения файла используется функция обратного вызова, которая вызывается сразу после завершения асинхронной операции чтения файла:

Функция обратного вызова выделена.

Аналогично тому, как было показано ранее, мы можем обернуть классическую конструкцию обратного вызова и заставить ее работать с более современным ожиданием:

Логика, которая должна быть выполнена после чтения файла, выделена красным прямоугольником.

Функция readFileAsync() возвращает промис, и await будет ждать разрешения этого промиса. Функция обратного вызова, переданная в fs.readFile(), такая же, как мы видели раньше: функция, которая просто разрешает [обещание] с результатом, переданным в функцию обратного вызова.

Функция обратного вызова всегда одна и та же: та, которая разрешается с результатом, переданным в функцию обратного вызова, или отклоняется с ошибкой, переданной в функцию обратного вызова.

В этом примере мне снова пришлось ввести асинхронную функцию, чтобы иметь возможность использовать await с вызовом асинхронной функции readFileAsync().

Ресурсы

StackOverflow при чтении файлов: https://stackoverflow.com/questions/46867517/how-to-read-file-with-async-await-properly

Статья Рохана Пола (Преобразование обратных вызовов JavaScript в Promise и Async-Await и замена метода Async-waterfall на Promise) на Medium https://medium.com/javascript-in-plain-english/converting-javascript-callbacks-to- обещание-и-асинхронное-ожидание-замены-асинхронного-метода-водопада-на-3c8b7487e0b9

Первоначально опубликовано на https://technology.amis.nl 11 января 2020 г.