Как ждать обещания с внутренним обещанием, которое будет решено? - PullRequest
0 голосов
/ 30 апреля 2020

У меня есть приложение express, и я хочу вернуть ответ только после того, как файл загружен и распакован. Мой код выглядит следующим образом:

function downloadObject() {
  return getObject(...).then((archive) => {
    console.log("Downloaded.");
    fs.writeFileSync(...);
    return decompress(..., function(err) {
      if (err) {
        console.log("Decompression error.");
      } else {
        console.log("Decompressed");
      }
    });
  })
}

app.post("/download", async function (req, res) {
  await downloadObject();
  res.send('ready');
});

Он ожидает загрузки файла, однако возвращает ответ, прежде чем файл будет распакован. Как я могу заставить его ждать выполнения внутреннего обещания?

Ответы [ 2 ]

2 голосов
/ 30 апреля 2020

Вы на самом деле возвращаете результат функции decompress, но это асинхронная функция c, поэтому возвращаемое значение на самом деле не имеет значения и, вероятно, undefined.

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

decompress(..., function(err, result) {
   // Do what you want with the error and the result
})

Чтобы использовать обещание вместо обратного вызова для выполнения асинхронной задачи, вы можете заключить свою функцию в обещание, как показано ниже, и так как Вы внутри then другого Обещания, оно будет цепочкой .

function downloadObject() {
  return getObject(...).then((archive) => {
    console.log("Downloaded.");
    fs.writeFileSync(...);
    return new Promise((resolve, reject) => 
      decompress(..., function(err, result) {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      })
    );
  })
}

Это еще не все, это работает, но мы можем добиться большего. writeFileSync заблокирует ваш IO в узле, и мы этого не хотим. Вместо этого предпочтительнее использовать writeFile, и все будет чище, если использовать цепочку Promise:

function downloadObject() {
  return getObject(...)
    .then((archive) => new Promise((resolve, reject) => {
       fs.writeFile("<filename>", archive?.data, '<file-encoding>', err => {
         if (err) {
           reject(err);
         } else {
           resolve(archive);
         }
       })
    }))
    .then((archive) => new Promise((resolve, reject) => {
      decompress(archive, function(err, result) {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      })
    }));
}

Конечно, это псевдокод, я не знаю, что находится в вашем archive объекте, я также не знаю сигнатуру типа вашей функции decompress, но важно показать, как обернуть функцию стиля обратного вызова в Promises, объединить их в цепочку, а также правильно обрабатывать ошибки с помощью функции reject, предоставляемой Promises.

1 голос
/ 30 апреля 2020

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

Используйте util.promisify, чтобы превратить ее в обычную функцию создания обещаний:

async function downloadObject() {
  const archive = await getObject(...);
  console.log("Downloaded.");
  fs.writeFileSync(...); // Or `util.promisify(fs.writeFile)`
  try {
    await util.promisify(decompress)(...);
    console.log("Decompressed");
  } catch (err) {
    console.log("Decompression error.");
  }
}

Эта функция реализует общий шаблон, который был упоминается в удаленном ответе:

util.promisify = function (f) {
    return function(...args) {
       return new Promise((resolve, reject) => {
          f(...args, (err, result) => { err ? reject(err) : resolve(result); });
       })
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...