#javascript #node.js
#javascript #node.js
Вопрос:
У меня есть две функции:
- Загружает файлы на сервер (время неизвестно)
- Запускает команду 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();
Надеюсь, это прояснит ситуацию!