Як зробити обіцянку з функції зворотного виклику в JavaScript

Бекенд-розробники постійно стикаються з проблемами під час створення додатків або тестування коду. Як розробник, який є досить новим і знайомиться з цими проблемами, я ніколи не стикався з викликом чи незручностями частіше - або більш запам'ятовується - ніж із функціями зворотного виклику.

Я не збираюся занадто глибоко вникати в деталі зворотного виклику та його плюси і мінуси або альтернативи, такі як обіцянки та асинхронізація / очікування. Для більш яскравого пояснення ви можете ознайомитися з цією статтею, яка докладно їх пояснює.

Пекло зворотного дзвінка

Зворотні дзвінки - корисна функція JavaScript, яка дозволяє здійснювати асинхронні дзвінки. Це функції, які зазвичай передаються як другий параметр іншій функції, яка отримує дані або виконує операцію введення-виведення, для завершення якої потрібен час.

Наприклад, спробуйте здійснити виклик API за допомогою requestмодуль або підключення до бази даних MongoDB. Але що, якщо обидва дзвінки залежать один від одного? Що робити, якщо дані, які ви отримуєте, - це URL-адреса MongoDB, до якої потрібно підключитися?

Вам доведеться вкласти ці дзвінки один в одного:

request.get(url, function(error, response, mongoUrl) { if(error) throw new Error("Error while fetching fetching data"); MongoClient.connect(mongoUrl, function(error, client) { if(error) throw new Error("MongoDB connection error"); console.log("Connected successfully to server"); const db = client.db("dbName"); // Do some application logic client.close(); }); });

Гаразд ... так де проблема? Ну, з одного боку, від цієї техніки страждає читабельність коду.

Спочатку може здатися нормальним, коли кодової бази мало. Але це погано масштабується, особливо якщо ви глибше заглиблюєтесь у вкладені зворотні виклики.

У підсумку ви отримаєте багато заключних дужок та фігурних дужок, які заплутають вас та інших розробників незалежно від того, наскільки акуратно відформатований ваш код. Існує веб-сайт під назвою callbackhell, який вирішує цю конкретну проблему.

Я чую, що деякі з вас, включаючи моє наївне минуле, кажуть мені загортати це в asyncфункція, то await функція зворотного виклику. Це просто не працює.

Якщо після функції, яка використовує зворотні виклики, є якийсь блок коду, цей код коду буде виконаний і НЕ чекатиме зворотного виклику.

Ось ця помилка, яку я зробив раніше:

var request = require('request'); // WRONG async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" await request.get(url, function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined }; // Wrong async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" request.get(url, await function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined };

Деякі досвідченіші розробники можуть сказати: "Просто використовуйте іншу бібліотеку, яка використовує обіцянки, щоб робити те саме, як аксіо,або просто скористатися fetch ” . Звичайно, я можу за цим сценарієм, але це просто тікає від проблеми.

До того ж це лише приклад. Іноді вас можуть заблокувати, використовуючи бібліотеку, яка не підтримує обіцянки, не маючи альтернативних варіантів. Як використання наборів для розробки програмного забезпечення (SDK) для спілкування з такими платформами, як Amazon Web Services (AWS), Twitter або Facebook.

Іноді навіть використання зворотного виклику для здійснення дуже простого дзвінка за допомогою швидкої операції вводу-виводу або CRUD - це нормально, і жодна інша логіка не залежить від його результатів. Але вас може обмежувати середовище виконання, як у функції Лямбда, яка вбиває весь процес після закінчення основного потоку, незалежно від будь-яких асинхронних викликів, які не завершились.

Рішення 1 (легко): Використовуйте модуль “util” Node

Рішення напрочуд просте. Навіть якщо вам трохи незручно з ідеєю обіцянок у JavaScript, вам сподобається, як ви можете вирішити цю проблему, використовуючи їх.

Як зазначають Erop і Robin в коментарях, Nodejs версії 8 і вище підтримує перетворення функцій зворотного виклику в обіцянки за допомогою вбудованого модуля util .

const request = require('request'); const util = require('util'); const url = "//api.chucknorris.io/jokes/random"; // Use the util to promisify the request method const getChuckNorrisFact = util.promisify(request); // Use the new method to call the API in a modern then/catch pattern getChuckNorrisFact(url).then(data => { let content = JSON.parse(data.body); console.log('Joke: ', content.value); }).catch(err => console.log('error: ', err))

Наведений вище код акуратно вирішує проблему за допомогою util.promisifyметод, доступний з базової бібліотеки nodejs.

Все, що вам потрібно зробити, це використовувати функцію зворотного виклику як аргумент util.promisify і зберігати в ній змінну. У моєму випадку це getChuckNorrisFact .

Потім ви використовуєте цю змінну як функцію, яку можна використовувати як обіцянку з .then () і .catch () методи.

Рішення 2 (залучено): Перетворіть зворотний дзвінок в обіцянку

Іноді використання бібліотек запитів та утиліт просто неможливо, чи то через застаріле середовище / базу коду, чи через запити з клієнтського браузера, вам доводиться обертати обіцянку навколо вашої функції зворотного виклику.

Давайте візьмемо приклад Чака Норріса вище і перетворимо це на обіцянку.

var request = require('request'); let url = "//api.chucknorris.io/jokes/random"; // A function that returns a promise to resolve into the data //fetched from the API or an error let getChuckNorrisFact = (url) => { return new Promise( (resolve, reject) => { request.get(url, function(error, response, data){ if (error) reject(error); let content = JSON.parse(data); let fact = content.value; resolve(fact); }) } ); }; getChuckNorrisFact(url).then( fact => console.log(fact) // actually outputs a string ).catch( error => console.(error) );

У наведеному вище коді я розміщую функцію зворотного виклику requestвсередині оболонки Promise Promise( (resolve, reject) => { //callback function}). Ця обгортка дозволяє нам викликати getChuckNorrisFactфункцію як обіцянку за допомогою методів .then()і .catch(). Коли getChuckNorrisFactвикликається, він виконує запит до API і чекає виконання resolve()або reject()оператора. У функції зворотного виклику ви просто передаєте отримані дані в методи вирішення або відхилення.

Після того, як дані (у даному випадку - дивовижний факт Чака Норріса) отримані та передані вирішувачеві, метод getChuckNorrisFactвиконується then(). Це поверне результат, який ви можете використовувати всередині функції всередині,then() щоб виконати бажану логіку - в цьому випадку відобразити її на консолі.

Детальніше про це ви можете прочитати у веб-документах MDN.