12

完全に理解できない問題に直面しています。私が把握していない概念、最適化できるコード、そしておそらく適切な方法で投入されたバグがあるように感じます。

全体の流れを大幅に簡素化するには:

  1. 外部 API に対して要求が行われる
  2. 返された JSON オブジェクトが解析され、リンク参照がスキャンされます
  3. リンク参照が見つかった場合、リンク参照を実際の JSON データに設定/置換する追加のリクエストが行われます。
  4. すべてのリンク参照が置き換えられると、元のリクエストが返され、コンテンツの構築に使用されます

元のリクエスト (#1) は次のとおりです。

await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])

Store.get は次のように表されます。

async get(type, id) {
    return await this._get(type, id);
}

どの呼び出し:

_get(type, id) {
    return new Promise(async (resolve, reject) => {
        var data = _json[id] = _json[id] || await this._api(type, id);

        console.log(data)

        if(isAsset(data)) {
            resolve(data);
        } else if(isEntry(data)) {
            await this._scan(data);

            resolve(data);
        } else {
            const error = 'Response is not entry/asset.';

            console.log(error);

            reject(error);
        }
    });
}

API 呼び出しは次のとおりです。

_api(type, id) {
    return new Promise((resolve, reject) => {
        Request('http://cdn.contentful.com/spaces/' + Constants.Contentful.SPACE + '/' + (!type || type === Constants.Contentful.ENTRY ? 'entries' : 'assets') + '/' + id + '?access_token=' + Constants.Contentful.PRODUCTION_TOKEN, (error, response, data) => {
            if(error) {
                console.log(error);

                reject(error);
            } else {
                data = JSON.parse(data);

                if(data.sys.type === Constants.Contentful.ERROR) {
                    console.log(data);

                    reject(data);
                } else {
                    resolve(data);
                }
            }
        });
    });
}

エントリが返されると、次のようにスキャンされます。

_scan(data) {
    return new Promise((resolve, reject) => {
        if(data && data.fields) {
            const keys = Object.keys(data.fields);

            keys.forEach(async (key, i) => {
                var val = data.fields[key];

                if(isLink(val)) {
                    var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);

                    this._inject(data.fields, key, undefined, child);
                } else if(isLinkArray(val)) {
                    var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));

                    children.forEach((child, index) => {
                        this._inject(data.fields, key, index, child);
                    });
                } else {
                    await new Promise((resolve) => setTimeout(resolve, 0));
                }

                if(i === keys.length - 1) {
                    resolve();
                }
            });
        } else {
            const error = 'Required data is unavailable.';

            console.log(error);

            reject(error);
        }
    });
}

リンク参照が見つかった場合、追加のリクエストが行われ、結果の JSON が参照の代わりに元の JSON に挿入されます。

_inject(fields, key, index, data) {
    if(isNaN(index)) {
        fields[key] = data;
    } else {
        fields[key][index] = data;
    }
}

注意してください、私はasyncawait、およびを使用しPromiseており、意図したマナーを信じています。最終的に何が起こるか:参照されるデータ (_scan の結果を取得) の呼び出しは、元の要求が返された後に発生します。これにより、コンテンツ テンプレートに不完全なデータが提供されることになります。

私のビルド設定に関する追加情報:

  • npm@2.14.2
  • node@4.0.0
  • webpack@1.12.2
  • babel@5.8.34
  • babel-loader@5.4.0
4

1 に答える 1

19

問題は でのforEach通話にあると思います_scan参考までに、 ES7 で非同期獣を飼いならす の次の文章を参照してください。

ただし、非同期関数を使用しようとすると、より微妙なバグが発生します。

let docs = [{}, {}, {}];

// WARNING: this won't work
docs.forEach(async function (doc, i) {
  await db.post(doc);
  console.log(i);
});
console.log('main loop done');

これはコンパイルされますが、問題はこれが出力されることです:

main loop done
0
1
2

何が起こっているかというと、await実際にはサブ関数にあるため、メイン関数が早期に終了しています。さらに、これは各 promise を同時に実行しますが、これは意図したものではありません。

教訓は次のとおりです。非同期関数内に関数がある場合は注意してください。はawait親関数を一時停止するだけなので、実際に実行していると思われることを実行していることを確認してください。

したがって、forEach呼び出しの各反復は同時に実行されます。一度に 1 つずつ実行しているわけではありません。基準に一致するものが終了するとすぐに、経由で呼び出された他の非同期関数がまだ実行されていてもi === keys.length - 1、promise は解決されて戻ります。_scanforEach

forEachを aに変更してmap、promise の配列を返すか (それらをすべて同時に実行し、すべてが完了await*_scanたら何かを呼び出したい場合)、またはそれらを 1 つずつ実行する必要があります。 -time 順番に実行する場合。


補足として、私がそれらを正しく読んでいれば、非同期関数の一部は少し単純化できます。関数呼び出しを呼び出すと値が返されますが、単純に呼び出すと別の promise が返され、関数から値を返すことは、非関数でその値に解決される promise を返すことと同じであるawaitことを覚えておいてください。たとえば、次のようになります。asyncasyncasync_get

async _get(type, id) {
  var data = _json[id] = _json[id] || await this._api(type, id);

  console.log(data)

  if (isAsset(data)) {
    return data;
  } else if (isEntry(data)) {
    await this._scan(data);
    return data;
  } else {
    const error = 'Response is not entry/asset.';
    console.log(error);
    throw error;
  }
}

同様に、次のように_scanすることもできます (forEachボディを同時に実行したい場合):

async _scan(data) {
  if (data && data.fields) {
    const keys = Object.keys(data.fields);

    const promises = keys.map(async (key, i) => {
      var val = data.fields[key];

      if (isLink(val)) {
        var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);

        this._inject(data.fields, key, undefined, child);
      } else if (isLinkArray(val)) {
        var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));

        children.forEach((child, index) => {
          this._inject(data.fields, key, index, child);
        });
      } else {
        await new Promise((resolve) => setTimeout(resolve, 0));
      }
    });

    await* promises;
  } else {
    const error = 'Required data is unavailable.';
    console.log(error);
    throw error;
  }
}
于 2015-12-19T07:03:30.983 に答える