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

ECMAScript представи Promises преди няколко години и го последва с чистия await и asyncсинтаксис. Това ни позволява да имаме асинхронно изпълнение като част от редовния програмен поток. Всичко подредено спретнато и последователно.

Все още обаче има много библиотеки, които работят с функции за обратно извикване в стар стил. Няма ли да е хубаво, ако можем по някакъв начин да накараме тези функции за обратно извикване да изчезнат и да направим всичко async & 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()

Номерът е в използването на Promise: функцията връща Promise и await знае как да се справи с Promise: тя изчаква обещанието да бъде разрешено (или отхвърлено) и произвежда резултата от разрешаването (и в тази ваза го присвоява на променлива msg. Обещанието в този случай е доста неприятно: то незабавно разрешава и връща съобщението.

Имайте предвид, че въведох функция be called(), която не беше там преди. Това не е необходимо: awaitможе да се използва само във функции, които са обозначени като async функции.

Резултатът от двете конструкции е един и същ:

Класически пример за използване на функция за обратно извикване е в setTimeout, когато дадена функция е планирана за изпълнение в по-късен момент. Тук функцията за обратно извикване, която трябва да бъде изпълнена след 1500 ms, е показана в червения правоъгълник.

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()

Отново логиката на [предишната] функция за обратно извикване е показана в правоъгълника.

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

Четене на файл

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

Функцията за обратно извикване е маркирана.

По начин, подобен на показания преди, можем да обвием класическата конструкция за обратно извикване и да я накараме да работи с по-модерно чакане:

Логиката, която трябва да се изпълни след като файлът бъде прочетен, е маркирана в червения правоъгълник.

Функцията readFileAsync() връща Promise и await ще изчака това Promise да се разреши. Функцията за обратно извикване, предадена на fs.readFile(), е същата, която сме виждали преди: функция, която просто разрешава [the Promise] с резултата, предаден на функцията за обратно извикване.

Функцията за обратно извикване винаги е една и съща: такава, която решава с резултата, предаден на функцията за обратно извикване — или отхвърля с грешката, предадена на функцията за обратно извикване.

В този пример отново трябваше да въведа асинхронна функция, за да мога да използвам 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- promise-and-async-await-replaceing-async-waterfall-method-with-3c8b7487e0b9

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