【问题标题】:Confused about Promises and async-await对 Promises 和 async-await 感到困惑
【发布时间】:2020-03-16 16:28:15
【问题描述】:

我正在使用 GitHub api 创建一个应用程序,但我在使用异步函数时遇到了问题。我是使用异步的新手,所以非常感谢您的帮助。这是我到目前为止编写的代码:

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 的行中的意外保留字。提前致谢

【问题讨论】:

  • 欢迎来到 SO!您不能使用await,除非它直接在带有async 关键字的函数内部。将files => { 更改为async files => {。此外,将let promises 设为二维数组有点奇怪——只需将map 的结果直接分配给它,否则您将拥有一个包含promises 数组的单元素数组。
  • 你的代码有很多问题,除了ggorlenArray.prototype.push指出的那个返回数组的长度,这几乎肯定不是什么你要。异步函数的返回值会自动包装在 Promise 中。 fetch API 已经返回了一个 promise,也不需要在那里手动构建一个。目前还不清楚为什么在您进行大量 .then 链接时使用 async/await,或者在使用 async/await 时为什么要使用 .then 链接。您的标题似乎未使用。您分配给函数参数等等等等。
  • @ggorlen 谢谢你的帮助。我不知道为什么我要制作一个二维数组。我想我正在失去理智。无论如何设法弄明白了,here's the working code.

标签: javascript async-await es6-promise


【解决方案1】:

这里有很多问题。代码很复杂;不需要所有这些承诺以及间接和嵌套层来实现您的需要。

你尝试做的模式很常见:

  • 发出请求以获取实体列表(文件、用户、网址...)。
  • 对于该列表中返回的每个实体,再次请求获取更多信息。
  • 将结果作为承诺返回(它必须是承诺,因为async 函数只能返回承诺)。

这样做的方法是将问题分成几个阶段。在大多数情况下,使用 awaitasync 关键字而不是 .then。为了使示例可重现,我将使用一个场景,我们希望获取在 GitHub 上创建的最新n gists 的用户配置文件——这基本上等同于你正在做的事情,我将把它留给你推断。

第一步是获取实体的初始列表(最近创建的 gists):

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

接下来,对于来自0..n 的 gist 数组中的每个 gist,我们需要触发一个请求。请务必确保我们不会在此处使用 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;
  }
})();

作为改进说明,最好在请求完成的同一阶段使用额外的 Promise 请求 JSON,但为简单起见,我们将分两个连续步骤进行。作为一个设计点,将负担过重的getRecentGistCreators 分成两个单独的步骤可能也不错。

【讨论】:

  • 谢谢你,我会听从你的指示的!
【解决方案2】:

我发现这是修改后的代码:

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;
};

我仍然不知道这是否是“正确”的做法,但它确实有效。

【讨论】:

    【解决方案3】:

    await 关键字只能与async 函数一起使用。如果您注意到,您的 await Promise.all(promises.map(promise => retFiles.push(promise))); 在一个函数中,该函数在 .then 中传递了 files 参数。只需使该功能 asyncawait 将在范围内工作。试试下面的代码。

     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;
    };
    

    【讨论】:

      【解决方案4】:

      所以我要在这里做一些假设,如果我错了,请纠正我,我会修正它们。希望这样做,我可以帮助澄清您的理解。

      考虑async/await 的一种简单方法是取代对.then(callback) 的需求。如果在async function 中,我更喜欢使用await

      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;
        }
      };
      

      此代码未经测试。我没有使用 github 的 api,所以我最好猜测每个调用在做什么。如果 gh.getReporepo.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 循环来编写答案的原因。所有的 Promise 基本上都是一次性执行的:

      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>

      【讨论】:

      • 感谢您的帮助我设法figure it out。只是为了澄清一下,GitHub api 不会返回 Promises,而是函数的最后一个参数是回调。
      • @KartikNair 除非您使用的是非常旧的版本,否则我真诚地怀疑情况是否如此。我的猜测是,如果您删除回调函数,它将返回一个承诺。
      • @ggorlen 我实际上是想记下这一点。在不知道有多少文件返回的情况下,我在此示例中采用了更保守的路径。我也主要集中在试图说明 async/await 是如何工作的。话虽如此,你是对的。这与 OP 最初写的不同。我会更新代码,使其更符合他提出的内容。
      • @KartikNair 我更新了我的答案,使其更符合您最初的答案。在省略回调时未返回承诺的情况下,我附加了一个解决方案(再次,我对此表示怀疑)。
      • 谢谢@Chance,你完全正确。我的错。感谢您提供的信息,我将把它与接受的答案结合使用来实现我的功能。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-28
      • 1970-01-01
      • 1970-01-01
      • 2016-09-19
      • 2013-03-29
      • 2014-11-02
      • 1970-01-01
      相关资源
      最近更新 更多