Как избежать использования «ада обратного вызова» и обратиться к обещаниям?

#javascript #node.js

#javascript #node.js

Вопрос:

У меня есть две функции:

  1. Загружает файлы на сервер (время неизвестно)
  2. Запускает команду ssh на сервере (почти 0,1 секунды)

Чтобы решить эту проблему, я использовал обратные вызовы следующим образом:

 const fs = require("fs");
const sftp = require("./_uploader/sftp");
const pm2 = require("./_uploader/pm2");

const credentials = {
  host: "",
  port: 123,
  username: "",
  key: "key",
};

sftp(credentials, () => {
  pm2(credentials, () => {
    console.log("done");
  });
});

  

sftp

 module.exports = ({ host, port, username, key }, cb) => {
  ...
  cb('done')
}
  

pm2

 module.exports = ({ host, port, username, key }, cb) => {
  ...
  cb('done')
}
  

Я знаю, что это можно сделать с помощью обещаний или асинхронных функций, но все мои попытки оказались безуспешными. Как это должно быть сделано правильно?

Комментарии:

1. Покажите «все мои попытки» и опишите, что сделало их неудачными — в противном случае, похоже, вы просто ждете, пока кто-нибудь напишет это за вас.

2. Очень хороший комментарий! Представьте, что я отменяю сотни действий.

3. Ну, ты все равно получил ответ — от службы написания кода SO.

Ответ №1:

Для этого есть разные способы. Я не буду охватывать их все.

ВАРИАНТ 1: ОЖИДАНИЕ ОБЕЩАНИЯ

Для этого примера вам нужно будет запустить код внутри async функции, чтобы воспользоваться await ключевым словом.

Обновите свои функции SMTP и PM2, чтобы возвращать обещание. Внутри обещания обрабатывается логика и resolve("done") освобождается обещание, чтобы код мог двигаться дальше.

 module.exports = ({ host, port, username, key }) => {
  return new Promise((resolve) => {
        ...
        resolve("done");
    }); 
}
  

Теперь вы можете обновить код выполнения, чтобы воспользоваться преимуществами обещаний:

 const fs = require("fs");
const sftp = require("./_uploader/sftp");
const pm2 = require("./_uploader/pm2");

const credentials = {
  host: "",
  port: 123,
  username: "",
  key: "key",
};


const runApp = async () => {
    await sftp(credentials);
    await pm2(credentials);
    console.log("done");
}

runApp();
  

ВАРИАНТ 2: ЦЕПОЧКА ОБЕЩАНИЙ

Другой способ сделать это — связать обещания в цепочку. Я предпочитаю не делать этого очень часто, потому что это может привести к путанице вложенной логики.

 const fs = require("fs");
const sftp = require("./_uploader/sftp");
const pm2 = require("./_uploader/pm2");

const credentials = {
  host: "",
  port: 123,
  username: "",
  key: "key",
};


sftp(credentials).then(result1 => {
    pm2(credentials).then(result2 => {
        console.log("done");    
    });
});
  

ВАРИАНТ 3: ПООБЕЩАТЬ ВСЕ

Другой вариант — использовать Promise.all

 const fs = require("fs");
const sftp = require("./_uploader/sftp");
const pm2 = require("./_uploader/pm2");

const credentials = {
  host: "",
  port: 123,
  username: "",
  key: "key",
};


Promise.all([
    sftp(credentials),
    pm2(credentials)
]).then(result => {
    console.log("done");    
});
  

Комментарии:

1. Это наилучший возможный ответ, и самое смешное, что я попробовал все три, но только сейчас я вижу свою ошибку. Ключевое слово RETURN в обоих файлах. omg…

2. Кстати, одна из попыток была: « module.exports = new Promise((resolve) => { ({ хост, порт, имя пользователя, ключ }) => { … разрешить («готово»); }); } « Как это связать? Правильно ли это вообще?

3. Ваш пример цепочки — это не цепочка, это вложенные обратные вызовы (вот почему он превращается в пирамиду гибели).

Ответ №2:

Давайте на мгновение забудем о async функциях. Это всего лишь немного синтаксического сахара поверх обещаний. Если у вас еще нет обещаний, то они не принесут вам никакой пользы.

Думайте о Promises как об объектах-оболочках, которые обрабатывают обратные вызовы для вас. На самом деле это не намного больше. Когда мы создаем новое обещание, нам передаются специальные resolve reject функции и . Затем мы можем использовать одну или обе эти функции вместо традиционных обратных вызовов. Так, например, если бы мы хотели promisify setTimeout:

 const timeoutAsPromised = (delay) => {
  return new Promise((resolve) => {
    setTimeout(resolve, delay);
  });
};
  

Мы немедленно возвращаем обещания. Это важно. Наша функция должна вернуть обещание сейчас, чтобы его можно было использовать сразу. Но вместо обратного вызова мы используем resolve функцию, которую дал нам конструктор Promise. Теперь мы можем назвать это так:

 timeoutAsPromised(1000)
  .then(() => console.log('One second has passed!'));
  

Для вашего варианта использования мы можем сделать почти то же самое. Просто возьмите свои функции и оберните их в обещанную версию:

 const sftpAsPromised = (credentials) => {
  return new Promise((resolve) => {
    sftp(credentials, resolve);
  });
};
  

Хотя, в зависимости от того, кто написал sftp и как, может быть так же легко переписать его с нуля, чтобы вернуть обещание вместо обратного вызова:

 module.exports = ({ host, port, username, key }) => {
  return new Promise((resolve) => {
    ...
    resolve('done')
  });
};
  

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

 const promisify = (fn) => (arg) => {
  return new Promise((resolve) => {
    fn(arg, resolve);
  });
};

const pm2AsPromised = promisify(pm2);
  

Хорошо! Теперь давайте кратко поговорим об асинхронности / ожидании. Это прекрасный синтаксис, но важно всегда помнить, что они работают только с обещаниями. Если у вас есть некоторые асинхронные функции, построенные на обратных вызовах, async / await для вас бесполезен.

К счастью, мы только что выполнили эту работу, пообещав наши функции обратного вызова. Итак, давайте создадим async функцию переноса, а затем await вызовем нашу обещанную функцию. Вы можете думать о await том, что в основном просто заменяете .then метод.

 const handlerServer = async () => {
  await sftpAsPromised(credentials);
  await pm2AsPromised(credentials);
};

handleServer();
  

Надеюсь, это прояснит ситуацию!