Как сделать Promise из функции обратного вызова в JavaScript

1656574210 kak sdelat promise iz funkczii obratnogo vyzova v javascript

Адхам Ель Банхави

FiZfnctqB6-ARf7GY7YCwptZRVxnUtaQZJv9

При создании приложений или тестировании кода разработчики бэк-энда постоянно сталкиваются с проблемами. Как разработчик, который достаточно новый и хорошо знаком с этими проблемами, я никогда не сталкивался с проблемами или неудобствами чаще — или запоминающимися — чем с функции обратного вызова

Я не собираюсь слишком глубоко вникать в детали обратного вызова и его плюсы и минусы или альтернативы, такие как обещания и асинхронность/ожидания. Чтобы получить более наглядное объяснение, вы можете ознакомиться с этой статьей, подробно объясняющей их.

Ад обратного звонка

Обратные вызовы представляют собой полезную функцию 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 = "
  
  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 = "
  
  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
    
};

Некоторые более опытные разработчики могут сказать: «Просто используйте другую библиотеку, которая использует обещания, чтобы делать то же самое, например axios, или просто используйте fetch». Конечно, я могу в таком случае, но это просто бегство от проблемы.

Кроме того, это только пример. Иногда вы можете быть заблокированы в использовании библиотеки, которая не поддерживает обещания без альтернатив. Как и использование комплектов для разработки программного обеспечения (SDK) для общения с такими платформами, как Amazon Web Services (AWS), Twitter или Facebook.

Иногда даже использование обратного вызова для выполнения очень простого вызова с быстрым вводом-выводом или операцией CRUD хорошо и никакая другая логика не зависит от его результатов. Но вы можете быть ограничены средой выполнения, как в функции Lambda, которая убивает весь процесс после завершения основного потока, независимо от каких-либо асинхронных вызовов, которые не завершились.

Решение 1 (простое): Используйте модуль Node “util”.

Решение удивительно простое. Даже если вам немного неудобная идея обещаний в JavaScript, вам понравится, как вы можете устранить эту проблему, используя их.

Как отмечают Erop и Robin в комментариях, Nodejs версии 8 и выше теперь поддерживают преобразование функций обратного вызова в обещания с помощью встроенного полезный модуль.

const request = require('request');

const util = require('util');

const url = ";

// 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.
Затем вы используете эту переменную как функцию, которую можно использовать как обещание с .потом() и .выловить() методы

Решение 2 (вовлечено): Превратите обратный звонок в обещание

Иногда использование библиотек запросов и util просто невозможно, будь то через устаревшую среду/кодовую базу или выполнение запросов из браузера на стороне клиента, вам придется повернуть обещание вокруг функции обратного вызова.

Давайте возьмем пример Чака Норриса выше и превратим это в обещание.

var request = require('request');
let url = ";

// 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)
);
ZXNYPRkv4mC2cHoq-4PIdoAx0WK-DyuUybzA
работает как магия

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

После того, как данные (в данном случае, удивительный факт Чака Норриса) будут получены и переданы резольверу, getChuckNorrisFact выполняет then() метод. Это вернет результат, который вы можете использовать внутри функции внутри then() чтобы выполнить нужную логику – в этом случае отобразить ее на консоли.

Вы можете прочитать больше об этом в веб-документах MDN.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *