Запутался в обещаниях и asyn c -wait - PullRequest
0 голосов
/ 16 марта 2020

Я делаю приложение, используя API GitHub, и у меня проблемы с функциями asyn c. Я новичок в использовании asyn c, поэтому я был бы очень признателен за помощь. Вот код, который я написал до сих пор:

const getFiles = async function(token, reponame) {
  var gh = new GitHub({
    token: token
  });

  reponame = reponame.split("/");
  const repo = gh.getRepo(reponame[0], reponame[1]);

  let head = new Headers();
  head.append("Authorization: ", "token " + token);

  const getContents = new Promise((res, rej) => {
    repo.getContents(null, "content", true, (err, files) => {
      if (err) rej(err);
      else return files;
    }).then(files => {
      let promises = [
        files.map(
          file =>
            new Promise(res => {
              fetch(file.downloadURL).then(body => {
                res(body.text);
              });
            })
        )
      ];

      const retFiles = [];
      await Promise.all(promises.map(promise => retFiles.push(promise)));
      res(retFiles)
    });
  });

  return getContents;
};

Ошибка, которую я получаю, является неожиданным зарезервированным словом в строке, где я использовал await. Заранее спасибо

Ответы [ 4 ]

0 голосов
/ 16 марта 2020

Здесь много вопросов. Код очень сложный; нет необходимости во всех этих обещаниях и слоях косвенности и вложенности, чтобы достичь того, что вам нужно.

Шаблон, который вы пытаетесь сделать, очень распространен:

  • Сделать запрос чтобы получить список объектов (файлы, пользователи, URL-адреса ...).
  • Для каждого объекта, возвращенного в этом списке, сделайте еще один запрос, чтобы получить для него дополнительную информацию.
  • Вернуть результат как обещание (это должно быть обещание, поскольку async функции могут только возвращать обещания).

Способ сделать это - разбить проблему на этапы. Используйте ключевые слова await и async вместо .then в большинстве случаев. Чтобы сделать этот пример воспроизводимым, я воспользуюсь сценарием, в котором мы хотим получить профили пользователей для самых последних n списков, созданных на GitHub - это в основном эквивалентно тому, что вы делаете, и я оставлю это вам для экстраполяции.

Первым шагом является получение начального списка сущностей (недавно созданных гист):

const res = await fetch("https://api.github.com/gists/public");
const gists = await res.json();

Далее, для каждого гиста в нашем массиве гист из 0..n, нам нужно выполнить запрос. Важно убедиться, что мы здесь ничего не сериализуем, используя await:

const requests = gists.slice(0, n).map(gist =>
  fetch(`https://api.github.com/users/${gist.owner.login}`)
);

Теперь, когда все запросы находятся в процессе, нам нужно дождаться их завершения. Вот где приходит Promise.all:

const responses = await Promise.all(requests);

Последний шаг - получение JSON от каждого ответа, для которого требуется еще Promise.all:

return await Promise.all(responses.map(e => e.json()));

Это наш окончательный результат, который можно вернуть. Вот код:

const getRecentGistCreators = async (n=1) => {
  try {
    const res = await fetch("https://api.github.com/gists/public");
    const gists = await res.json();
    const requests = gists.slice(0, n).map(gist =>
      fetch(`https://api.github.com/users/${gist.owner.login}`)
    );
    const responses = await Promise.all(requests);
    return await Promise.all(responses.map(e => e.json()));
  }
  catch (err) {
    throw err;
  }
};

(async () => {
  try {
    for (const user of await getRecentGistCreators(5)) {
      const elem = document.createElement("div");
      elem.textContent = user.name;
      document.body.appendChild(elem);
    }
  }
  catch (err) {
    throw err;
  }
})();

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

0 голосов
/ 16 марта 2020

Я понял, что это пересмотренный код:

const getFiles = async function(token, reponame) {
  var gh = new GitHub({
    token: token
  });

  reponame = reponame.split("/");
  const repo = gh.getRepo(reponame[0], reponame[1]);

  let files = new Promise((res, rej) => {
    repo.getContents(null, "content", true, (err, files) => {
      if (err) rej(err);
      else res(files);
    });
  });

  let content = new Promise(res => {
    files.then(files => {
      const promises = files.reduce((result, file) => {
        if (file.name.endsWith(".md")) {
          result.push(
            new Promise((res, rej) => {
              repo.getContents(null, file.path, true, (err, content) => {
                if (err) rej(err);
                else
                  res({
                    path: file.path,
                    content: content
                  });
              });
            })
          );
        }
        return result;
      }, []);

      console.log(promises);

      res(
        Promise.all(
          promises.map(promise =>
            promise.then(file => {
              return file;
            })
          )
        )
      );
    });
  });

  return await content;
};

Я до сих пор не знаю, является ли это "правильным" способом сделать это, но он работает.

0 голосов
/ 16 марта 2020

Ключевое слово await может использоваться только с функцией async. Если вы заметили, что ваш await Promise.all(promises.map(promise => retFiles.push(promise))); находится внутри функции, которая передает files параметр в .then. Просто сделайте так, чтобы функции async и await работали внутри области. Попробуйте следующий код.

 const getFiles = async function(token, reponame) {
  var gh = new GitHub({
    token: token
  });

  reponame = reponame.split("/");
  const repo = gh.getRepo(reponame[0], reponame[1]);

  let head = new Headers();
  head.append("Authorization: ", "token " + token);

  const getContents = new Promise((res, rej) => {
    repo.getContents(null, "content", true, (err, files) => {
      if (err) rej(err);
      else return files;
    }).then( async (files) => {
      let promises = [
        files.map(
          file =>
            new Promise(res => {
              fetch(file.downloadURL).then(body => {
                res(body.text);
              });
            })
        )
      ];

      const retFiles = [];
      await Promise.all(promises.map(promise => retFiles.push(promise)));
      res(retFiles)
    });
  });

  return getContents;
};
0 голосов
/ 16 марта 2020

Итак, я собираюсь сделать некоторые предположения здесь, так что поправьте меня, если я ошибаюсь, и я исправлю их. Надеюсь, что в этом я помогу прояснить ваше понимание.

Простой способ думать о async/await заменяет необходимость в .then(callback). Я предпочитаю использовать await, если в async function.

const getFiles = async function(token, reponame) {
  try {
    var gh = new GitHub({
      token: token
    });
    reponame = reponame.split("/");

    // edit: I removed await from the line below as your original code
    //       did not treat it as a promise
    const repo = gh.getRepo(reponame[0], reponame[1]); 

    // unused code from your post
    let head = new Headers();
    head.append("Authorization: ", "token " + token);

    // the await below assumes that repo.getContents 
    // will return a promise if a callback is not provided
    const files = await repo.getContents(null, "content", true); 

    // updating the code below so that the file requests run in parallel.
    // this means that all requests are going to fire off basically at once
    // as each fetch is called
    const fileRequests = files.map(file => fetch(file.downloadURL))

    // you wont know which failed with the below.
    const results = (await Promise.all(fileRequests)).map(res => res.text)
    return results
  } catch (err) {
    // handle error or..
    throw err;
  }
};

Этот код не проверен. Я не использовал API GitHub, поэтому я думаю, что делает каждый звонок. Если gh.getRepo или repo.getContents не возвращают обещания, потребуется некоторая корректировка.

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

const getFiles = async function(token, reponame) {
  try {
    var gh = new GitHub({
      token: token
    });
    reponame = reponame.split("/");
    const repo = await gh.getRepo(reponame[0], reponame[1]); 
    let head = new Headers();
    head.append("Authorization: ", "token " + token);

    const getContents = new Promise((res, rej) => {
      repo.getContents(null, "content", true, (err, files) => {
        if (err) { 
          return rej(err);
        }
        res(files)
      })
    })
    const fileRequests = (await getContents).map(file => fetch(file.downloadURL))
    return (await Promise.all(fileRequests)).map(res => res.text)
  } catch (err) {
    // handle error or..
    throw err;
  }
};

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

const content = document.getElementById("content")
const result = document.getElementById("result")

async function example(){
  content.innerHTML = 'before promise';
  const getContents = new Promise((res, rej) => {
    setTimeout(()=> {
      res('done')
    }, 1000)
  })
  const res = await getContents
  content.innerHTML = res
  return res
}
example().then((res)=> {
  result.innerHTML = `I finished with <em>${res}</em> as a result`
})
<div id="content"></div>
<div id="result"></div>

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

const content = document.getElementById("content")
const result = document.getElementById("result")
async function example() {
  const promises = []
  content.innerHTML = 'before for loop. each promise updates the content as it finishes.'
  for(let i = 0; i < 20; i++){
    const promise = new Promise((resolve, reject) => {
      setTimeout(()=> {
        content.innerHTML = `current value of i: ${i}`
        resolve(i)
      }, 1000)
    })
    promises.push(promise)
  }
  const results = await Promise.all(promises)
  return results
}

example().then(res => result.innerHTML= res.join(', '))
  content:
  <div id="content"></div>
  
  result:
  <div id="result"></div>
...