75437


Next.jsでブログを作成しています。下記のコードはGitHubリポジトリの内容を取得して、それを加工して `datas`
に格納したいのですが、上手く行きません。  
`console.log(datas)` しても `undefined`と表示されます。詳しい方見ていただけないでしょうか。よろしくお願いします。

[code]

    const datas = await (async (zennArticles) => {
        if (zennArticles) {
          await Promise.all(zennArticles.map(async (article: Article) => {
            const data = await fetch("https://api.github.com/repos/wimpykid719/zenn-content/contents/articles/" + article.name, {
              headers: {"Authorization": "token xxxxxxxxxxxxxxxxxxx"}
            })
              .then(res => {
                  return res.json();
              })
              .catch(err => {
                  console.log(err);
              });
            const buffer = Buffer.from(data.content, 'base64');
            const fileContents = buffer.toString("utf-8");
            const matterResult = matter(fileContents)
            const processedContent = await remark()
              .use(html)
              .process(matterResult.content)
            const contentHtml = processedContent.toString()
            return {
              id: article.name,
              ...(matterResult.data as { title: string; emoji: string; type: string; topics: string[]; published: boolean }),
              content: contentHtml,
            }
          }));
        }
      })(zennArticles);
      console.log(datas)
    
    //実行結果
    undefined
    
[/code]


CC BY-SA 4.0
2021-04-21T12:53:33.583
75431
2021-04-22T04:58:03.770
2021-04-22T04:58:03.770
3060
22565
question
javascript typescript promise next.js
JavaScript（TypeScript）でPromise.allの戻り値を変数に格納出来ない。


Promiseに関する質問かと思いますので、本質的でない remarkjs(?) などの処理を除外して考えると以下の様になるかと思います。

[code]

    type Article = {
      name: string
      sha: string
      size: number
      url: string
      html_url: string
      git_url: string
      download_url: string
      type: string
    };
    
    type Content = {
      name: string
      path: string
      sha: string
      size: number
      url: string
      html_url: string
      git_url: string
      download_url: string
      type: string
      content: string
      encoding: string
    };
    
[/code]

[code]

    let data: ReadonlyArray<Record<string, unknown>> = [];
    const setData = (values: ReadonlyArray<Record<string, unknown>>) => { data = values; }
    
    const getArticles = async (): Promise<ReadonlyArray<Article>> =>
      fetch("https://api.github.com/repos/wimpykid719/zenn-content/contents/articles/")
        .then(res => res.json())
        .catch(err => {
          console.error(err);
          return [];
        });
    
    const getContents = async (zennArticles: ReadonlyArray<Article>): Promise<ReadonlyArray<Content>> => {
        if (zennArticles.length > 0) {
          return Promise.all(
            zennArticles
            .map(async (article: Article) => {
              return fetch(`https://api.github.com/repos/wimpykid719/zenn-content/contents/articles/${article.name}`)
                .then(res => res.json())
          })).catch(err => {
            console.error(err);
            return [];
          });
        } else {
          return [];
        }
      }
    
    const getData = async (): Promise<ReadonlyArray<Record<string, unknown>>> => {
      const articles = await getArticles();
      const contents = await getContents(articles);
    
      // FIXME: remarkjs で　content を HTML に変換したりなどする。
      //        関数などを定義して呼び出してもよい。
      // const data = await parseContents(contents);
      // return data;
    
      console.log(contents);
    
      return contents;
    }
    
    getData().then(setData);
    
[/code]

[上記コードのTypeScript
Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAggTsAlgYwDbQLxQN4CgpQB2AhgLYQBcUAzsHIoQOb40AWxVt9TL1iAXpSIBXUgCMIcFsLipOdBswKtgpVAH0Zcmgp4FGiYJtnzuSqABMA9gHdCqK8QvHtXRS1CRT7gL4BuXFxPaABhK0JgCAioLDwCEnJvPSgwYmBWJPNqdkzeASFCUQkpAi1c5VUNMp0zFgMjardk6zsHJxdyqGDO5HDIiM6o3otFXP9A9GBLNI4oACUIJ3DUEHg4YhAAHgXeuAtNpsYAGihhQgBrQltCAD4bmKgAbQBdAN7CWhoIYAARGYeABQAN2IqGEEGoVAWS3sqzg6y2OysewOumOpwuVzsdwAlDF7thpsBiA8QWCIX4oD5Au9PoxvvAkGgIQ9iNQQIRkFAATiqAAFOBWUiIagQbaLaywtYbTaMlDoO74lgAM2+yFYAIARCpgGBIQB6fXEMCIAB09VYwjEpt6pH1cAgYCs1H1NkQpDAIHOiAsAHYAIwATn1gkIhAAtO9+sB9VGosAXcQEPKIfrNTiWARTekogCHdR8VB86aAFbUcI8jMELPINLqgGSOCFuLVgi0qzoU2N5EN+E4gKtggO4AyQhPV6Zqn9mnhOnfMIReMFrBsjlcgGhwhy5mQ+YS5ZwhGy5PMm68qACoUisXQyUraVbBfRxUYAmTxDK7mb7foaim9BMOkUD3AADHiLatsOo4XoKwqiqaoKoACk6tt+J6-ih1amqQxoAqunLckmTLoFQP4QHir44JhkHfNBqrAPWAAGOp6hQhrGmaFpWjaQr2o6zquu6nren6QYhlEEZxhEsZ9EuRroamAAk2BESmpoJBAPiMVWg66dmrC5vmhbFmWFY4jprY+OZNp1hqjbNtRUDtp23ZwL2cDTrpQ60XAY4vAOg5WQFBA+FAECoKKVG6VBvnjsFVIsNSuC0lM9K-P8K7sgRPL8rB17ijC97wjKSIoocJxnJc1x3PclEtilUCqTurI2MQhhQGlZHUDyAUNVJCYtW1qXzrJETdU1v7TiwhpQAAYgAkgAGgAsgAolQDo4XA5xllAgDmDIAAAz9VAgBJDFAAASAAqy0ADJQIA1gyAJCagDbxoA6gyAPoMgBRDIAVgyAJYMgCaDIA0QyAEAM036l5UCAEWpgAOpn9J2AFnagCV-q9gBmDIAPiqAM4MgBfiqjgBBDIAEQyACIMoMEDNDUWBljWte1qRwKKT5LgC-
XUJ5UAzTFY4U8SAQsM5ED-lYjBM6NCZTd5I6xczARJWlfzEjy+m5qK6XEtOQA)で動作を確認してみてください。

※ 極力コードが短くなるように一部変更しています  
※ `Record<string, unknown>` は `{}` と同義です。([`typescript-eslint#ban-
types`](https://github.com/typescript-eslint/typescript-
eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md))  
※ `ReadonlyArray<>` は、この型にしておくと、その配列を直接変更するようなメソッド呼び出しができなくなります。

* * *

**質問に書かれているコードについて**

  * catchで値を返すとそれ以降の `then()` でその値が返ります 
    * 今回は、error loggingだけしておいて、とりあえず空配列などを返しておけばよいと思います
  * `async`関数では必ず「値」か「Promise」を返しましょう 
    * 値を返した場合、`Promise.resolve()`でその値を返した扱いになります
    * 「`Promise.all()`が返すPromise」や「`zennArticles`がfalsyの場合」などで一切returnをしていないので、このasync関数は `(zennArticles: Array<Article>) => Promise<void>`になっています 
      * なので、このasync関数の `then()` の戻り値は必ず `undefined` になるかと思います
    * 慣れないうちは、async関数の戻り値の型を書いておくことをおすすめします 
      * その型の戻り値を返していない場合に、コンパイルが通らなくなります
  * 回答のように`async`関数を単一機能ごとに分けて定義すれば、動作確認や問題の切り分けが楽になりそうな気がします
  * `Promise.all()`は1つでも失敗すると、全体が失敗します 
    * 個別のエラー処理が不要であれば、`Promise.all()`の後で`catch()`すればいいかと思います

[code]

    Promise.all([
      Promise.resolve(0),
      Promise.resolve(1),
      Promise.reject(new Error("Error in 2")),
      Promise.resolve(3),
      Promise.resolve(4)
    ])
      .then(numbers => { console.log(numbers); })
      .catch(e => {
        console.error(e);
        return [];
      });
[/code]

* * *

**余談**

`Promise.all()`でエラーが起きたものだけ除外したい場合、各`Promise`でArrayを返して型を`T[][]`にしておき、取得するときに`Array.prototype.flat()`で`T[]`に変換してしまう方法などもあります。

[code]

    const promises = [0, 1, 2, 3, 4, 5, 6].map(n => {
      if (n % 2 === 0) {
        // 値を配列に包んで返す
        return Promise.resolve([n]);
      } else {
        // エラーが起きたものは空配列を返すようにしておく
        // -> 2次元配列を .flat() した時に消える
        return Promise.reject(new Error(`Error in ${n}`))
          .catch(e => {
            console.error(e);
            return [];
          });
      }
    });
    
    Promise.all(promises)
      .then(numbersNestedArray => numbersNestedArray.flat())
      .then(numbers => { console.log(numbers); });
[/code]


CC BY-SA 4.0
2021-04-21T17:54:11.803
75437
2021-04-21T19:45:09.527
2021-04-21T19:45:09.527
3068
3068
75431
answer