@Esperanto

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

promise chain にすべきか async await にすべきか。。。

解決したいこと

前の promise の結果に依存する後続の promise 実行する場合、どのように実装するのが良いでしょうか?

例)

const promiseA = (): Promise<string> => {
  return new Promise((resolve) => {
    resolve('A')
  });
}

const promiseB = (param: string): Promise<string> => {
  return new Promise((resolve) => {
    resolve(`${param} だったよ`)
  });
}

という promise があった時

// promise chain を使うパターン
const doPromiseChain = () => {
  return promiseA().then((result) => {
    return promiseB(result)
  })
}

console.log(doPromiseChain()) // A だったよ

とすべきか、

// async/await を使うパターン
const doPromiseAwait = async () => {
  const resultA = await promiseA()
  const resultB = await promiseB(resultA)
  return resultB
}

console.log(doPromiseAwait()) // A だったよ

とすべきか悩んでいます :thinking:

promiseA()promiseB() がお互いに依存しない場合は async/await にするのが良いと思いますが、promiseA() の結果を待って promiseB() を実行するのであれば、promise chain にしたほうが見やすいのかなと... :thinking:

みなさまのご意見を伺いたいです :bow:

0 likes

つべこべ言わずにasync await try/catch です。

というと雑ですが、そちらで正しい設計をすると非常に見やすく、例外を直列/並列共に扱いやすくなります(特に並列のケース)

async/awaitはプロミスチェーンのシンタックスシュガーとして作られました(=無くてもできるのに実装された)。そして今日まで特に不都合なく皆に愛されています。

つまりそういうことです。

1Like

Promiseチェーンを使ってまで実行機序を明確化したいケース自体稀ではないかと思われます.
エラーはどうせcatchするので……
それよかawait使った方が圧倒的にdoPromiseAwaitの返値が分かりやすいです.

今日日ES2017を動かせない環境もほぼ無いです.

2Like

async を使う絶大なメリットの一つに、関数の返り値が絶対にpromiseになるということがあります。

いるんですよ、たまに。return nullやらオブジェクトやらとreturn Promiseを同居させて、thenをそのまま使うとエラーにしてくるやつとか、、、

promise返すなら絶対にpromiseを返すべきです。これを保証してくれるasyncを神と崇めなさい。

1Like

@tonberry1050 @Verclene
早速のご回答ありがとうございます!

確かにお二人の仰る通り promise が返却されることが保証されている async/await を使うべきというのはその通りですね!

const が増えると個人的に「見づらいなあ」と思っていたのですが、慣れの問題っぽいので頑張って async/await に慣れます :muscle:

0Like

asyncが使えるならawait、使えないなら.then。

// asyncは必ずPromiseを返す
const promiseA = async () => 'A';
const promiseB = async param => `${param} だったよ`;

// async/await を使うパターン、await promiseB せずにそのままPromiseを返しても同じ結果
const doPromiseAwait = async () => promiseB(await promiseA());

// doPromiseAwaitはPromiseなのでawaitするか.thenで処理しなければいけない
doPromiseAwait().then(result => console.log(result)); // A だったよ
1Like

@Esperanto さん

達成条件

promise chain にすべきか async await にすべきか。。。

「すべき」と書いていますが、達成条件が設定されていないので、「どちらでもよい」が答えとなります。
べき論に持っていくには「要件」を明確にすることが重要です。

逐次処理

私は逐次処理に拡張性を持たせたいので、管理関数を作って実装します。
(promiseA,promiseBの catch が存在する可能性がありますが、質問文のコードに限るなら、下記コードで要件を満たせます。)

'use strict';
const promiseA = () => {
  return new Promise((resolve) => {
    resolve('A')
  });
}

const promiseB = (param) => {
  return new Promise((resolve) => {
    resolve(`${param} だったよ`)
  });
}

const executeSerialPromise = taskList => {
  let promise = Promise.resolve();

  for (let task of taskList) {
    promise = promise.then(task);
  }

  return promise;
};

executeSerialPromise([promiseA, promiseB]).then(result => console.log(result)).catch(error => console.error(error));;

あと、「Promiseの本」が逐次処理実装時の参考になると思います。

1Like

これだけは強く布教したい領域です。「Promiseを並列にしたい」とかなんとかとか言い出した時とか、絶対にasync-awaitで設計したほうが楽です。なんかあった時、awaitを抜くだけで設計できます。
Promiseのことをthenで取り出すもの、ではなく待つことも放置することもできるオブジェクトとして理解すると1つのブレイクスルーになります。

(async () => { // 直列
  const pro1 = await new Promise(res => setTimeout(res, 1000));
  const pro2 = await new Promise(res => setTimeout(res, 1000));
})();

(async () => { // 並列
  const pro1 = new Promise(res => setTimeout(res, 1000));
  const pro2 = new Promise(res => setTimeout(res, 1000));
  await Promise.allSettled([pro1, pro2]);
})();


// 更に配列要素(例えば配列で渡されたIDについてそれぞれAPIを呼ぶとかしますよ)
const arr = [1000,2000,3000,4000,];
async function promiseFn(ms) { // 例え無くても良くてもasyncを明示的につける
  return new Promise(res => setTimeout(res, ms));
}
(async () => { // 直列
  for(const val of arr) {
    await promiseFn(val);
  }
})();
(async () => { // 並列
  const proArr = arr.map(promiseFn);
  await Promise.allSettled(proArr);
})();

for of awaitmapは古のfor i++と同じくらい固定された文法です

追記:
つまり、thenで書くプログラミングをしていても、Promiseを一度変数に入れる処理や、取り出す前に配列に入れるような処理がそのうち必要になります。
であればawaitに慣れた方が早いです

1Like

布教の意図はありませんが、並列処理には Promise.all, Promise.allSettled が存在するので、コーディングで考える部分はあまりないと思います。

'use strict';
const promiseA = () => {
  return new Promise((resolve) => {
    resolve('A')
  });
}

const promiseB = (param) => {
  return new Promise((resolve) => {
    resolve(`${param} だったよ`)
  });
}

Promise.all([promiseA(), promiseB()])
  .then(results => {
    for (let result of results) {
      console.log(result);
    }
  }).catch(error => console.error(error));

ここから逐次処理に書き換える時に、Promise.all と似た設計であれば、コード書き換えはほぼ発生しません。
先の逐次処理コードは Promise.all を意識して書いているので、書き換え箇所は少ないと思います。

1Like

Your answer might help someone💌