Функции обратного вызова были очень распространены в приложениях 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 г.