質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

コーディング規約

コーディング規約とは、コードの書き方についての決め事のことです。 文法のことではなく、そのチームなどの中の約束事としてどのような書き方で行うかを定めるもの。 項目の例として、関数や変数の命名規則、コーディングのスタイル、括弧やインデントの書き方などが挙げられます。

Q&A

解決済

1回答

3443閲覧

【Node.js】メソッドチェーン vs try-catch どっちの記法がベスト?

PePePeT_i_C

総合スコア31

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

コーディング規約

コーディング規約とは、コードの書き方についての決め事のことです。 文法のことではなく、そのチームなどの中の約束事としてどのような書き方で行うかを定めるもの。 項目の例として、関数や変数の命名規則、コーディングのスタイル、括弧やインデントの書き方などが挙げられます。

2グッド

6クリップ

投稿2021/10/05 08:11

編集2021/10/05 08:13

経緯

  • タイトルのどちらも似たような処理になる認識ですが、SIerで開発をしているPGとして

Promiseオブジェクトをハンドリングはどちらの記法で実装することがベストなのか興味を持ち投稿いたしました

  • 三項演算子を使うべきかどうかと似たようなケースになると思いますが、ご意見・ご回答をくだされば幸いです

前提

  • SIerで追加開発・保守運用をするという観点からご意見・ご回答をいただければと思います
  • 現場のレベルはJavaがメインで、Node.jsは未経験者がほとんどという想定でお願いします

❓質問事項

Node.js Promise オブジェクトのハンドリングとして、以下2種類の書き方があります
どちらの記法がベストな記法なのかを下記の観点よりご教授いただけないでしょうか?

  1. async/await を用いたtry-catch構文でのハンドリング
  2. then()/catch()を用いたメソッドチェーンでのハンドリング

観点

  • 可読性
  • 視認性
  • 保守/メンテナンス性
  • 拡張性
  • その他 追加開発/保守運用で必要な諸々。。。

該当のソースコード

javascript

1import axios from "axios"; 2 3// async/await を用いたtry-catch構文でのハンドリング 4export const getData = async () => { 5 const url = "https://xxxxx"; 6 try { 7 const res = await axios.get(url); 8 // handle success 9 console.log(`Response : ${res.data}`); 10 } catch (error) { 11 console.error(`Error : ${error}`); 12 } 13} 14 15// then()/catch()を用いたメソッドチェーンでのハンドリング 16export const getData = () => { 17 const url = "https://xxxxx"; 18 axios 19 .get(url) 20 .then((res) => { 21 // handle success 22 console.log(`Response : ${res.data}`); 23 }) 24 .catch((err) => { 25 console.error(`Error : ${err}`); 26 }); 27}; 28
ktl2018, hon.ki👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

どちらの記法がベストな記法なのか

混在させると悲惨な事になるので
プロジェクトの既存コードに合わせるのが大前提です。

ただし、プロジェクトを立ち上げる場面だとしましょう。
採用条件を選べるのなら絶対にasync/awaitの方を選びます。

ベースはasync/await、
要所要所でPromise流儀の書き方を強いられるので
必要最低限のみPromise流儀の書き方で解決するようにします。


何故そうなるのかに関しては歴史を紐解く必要があるので、そちらの解説からします。

まずはコールバック地獄のお話。
質問文で最初から「Promise使いたいです!」って言っているので恐らく既知の箇所と被ってるでしょう。
聞き流してやってください。

JavaScript(Node.js)はシングルスレッドです。
とりあえず書いてある事は一息で実行しようとします。

なのでネットワークやHDDへのアクセスは基本的には待ちません。
関数と達成条件をセットで渡してイベント登録を行い、
「イベントループ」という巡回している機能に代わりに関数を叩いてもらう設計になっています。

まぁ、これが原因で「コールバック地獄」になるんですがね。

JSがシングルスレッドであるがために既存のコードを一息で実行しようとして、
怠惰で後回しにするイベント登録という解決策しか用意されてない以上、
非同期処理を挟む時は、同期処理に帰って来れないのでif・for・try-catchといった制御構文が一切使えません。
(抽出しようと思っても値が帰ってくるまで待たず、持って帰れないので全ての制御構文がすり抜ける)

もし貴方の現場のメンバー全員が、
JavaScriptのルーツはLispなので、それのテクニックを流用してサラッと書けると思いますが、
普段からHaskellやLispで書きまくっている関数型オタクならいざ知らず
手続き型プログラミング言語のJavaを使っている人には相当無理がある要求です。

多分現場のコードはネストだらけの阿鼻叫喚のクソみたいなコードであふれかえるでしょう。


Promiseはその「コールバック地獄」を
オブジェクト指向プログラミングのテクニックで解決したものです。

PromiseインスタンスはES2016で正式にJSの仕様として採用されており、
Node.jsなら1系未満でも対応しています。

Promiseのインスタンスに対して.then(fn).catch(fn)メソッドを叩くだけなので
初期のJSのコールバック地獄から比べれば雲泥の差です。

しかし、所詮はオブジェクト指向プログラミングのテクニックでゴリ押ししただけ。
非同期処理を挟む時は、同期処理に帰って来れないのでif・for・try-catchといった制御構文が一切使えません。
この大問題は一生解決しません。(←これが言いたいからコールバック地獄の話した)

コードで説明しましょう。

js

1// このURL一覧にアクセスしてHTMLを持ち帰りたい 2const urls = [ 3 "http://example.com/1", 4 "http://example.com/2", 5 "http://example.com/3", 6 "http://example.com/4", 7]; 8 9// こう書けばよくね? 10Promise.all( 11 urls.map(url => 12 axios.get(url).then(res => res.data) 13 ) 14).then(results => { 15 console.log(results); 16}); 17 18// → 同時接続しすぎなので一度に1通信だけにして欲しいと顧客に言われた 19// しゃあない、for文使って数珠つなぎにするか 20let promise = Promise.resolve(null); 21const results = []; 22for (const url of urls) { 23 promise = promise 24 .then(() => axios.get(url)) 25 .then(res => results.push(res.data)) 26 }); 27} 28// 同期処理には帰ってこれないのでresultsの中身を見たければpromise.thenを叩くしか無い 29promise.then(() => { 30 console.log(results); 31});

なんか雲行きが怪しくなってきましたね。
あっ、これにエラー処理追加しないといけないですね。

js

1let promise = Promise.resolve(null); 2const results = []; 3for (const url of urls) { 4 promise = promise 5 .then(() => axios.get(url)) 6 .then(res => results.push(res.data)) 7 // ココに書くと失敗したURLを飛ばして次に行く 8 .catch(err => console.log(err)); 9} 10promise 11 // 上記ではなくここだけに書くと、失敗したらそれ以降通信しない 12 .catch(err => console.log(err)) 13 .then(results => console.log(results));

(一発書きなので多分動くと思いますが自信薄)

エラー処理挟むだけで2通りに分岐しましたね。
リーダーの貴方は部下が書いてきたこのコードを読んで、
「仕様と違うよね?直して」「仕様通りだね、さすが!」を判別しなければなりません。

私は毎日こんなコード読みたくありません。

可変長の配列のURLを順次アクセスするもっとスマートに書く方法があるかもしれないですが、
私のJS力だとちょっとこれが限界ですね。
私だったら「やりたいことに対するノイズ多すぎ、やってられるか!」と思いますね。

Promiseには限界があると思います。


んでようやくasync/await構文
これはES2017のPromiseの次に実装されたPromiseをよしなに操る糖衣構文です。

async関数を定義すると、必ずPromiseのインスタンスを返します。

  • await Promise: .then(val => {})に変換され、valを同期処理っぽく取り出せる
  • return val: resolve(val)に変換される
  • try-catch: .catch(中身)に変換される
  • if文: 謎の力でよしなにやってくれる!
  • for文: 謎の力でよしなにやってくれる!

もうね、if,for,try-catchが非同期処理に混ぜて良いだけで神ですよ。
出るの1年おせえよES2016の時点で実装しろよ。

いやいや出てくれただけで神です。

js

1// async関数を定義しないとawait構文使えないからね 2const main = async () => { 3 const urls = [ 4 "http://example.com/1", 5 "http://example.com/2", 6 "http://example.com/3", 7 "http://example.com/4", 8 ]; 9 10 // 最初に書いたPromise.allを使うパターンも何気に恩恵受けてる 11 // Promise.allはエラー処理周りで融通効かないので質問文の条件を加味すると頻度は下がると思う 12 const results1 = await Promise.all( 13 urls.map(async url => { 14 const res = await axios.get(url); 15 console.log(res.data); 16 reutrn res.data; 17 }) 18 ); 19 console.log(results1); 20 21 // 途中でエラー吐いたら即停止したい 22 const results2 = []; 23 try { 24 for (const url of urls) { 25 const res = await axios.get(url); 26 console.log(res.data); 27 results2.push(res.data); 28 } 29 console.log(results2); 30 } catch (e) { 31 console.error(e); 32 } 33 34 // エラー吐いたURLは無視して次へ行きたい 35 const results3 = []; 36 for (const url of urls) { 37 try { 38 const res = await axios.get(url); 39 console.log(res.data); 40 results3.push(res.data); 41 } catch (e) { 42 console.error(e); 43 } 44 } 45 console.log(results3); 46}; 47 48// 実行 49main(); 50 51// こんな風にasyncのアロー関数を括弧で包んで即時実行しても良い 52(async () => { 53 // 内部の処理 54})();

内部的にPromiseを使いまくっているとはいえ、
Promise臭みたいなのはコードからほぼ消えました。
Promiseだけで全部やれ!の地獄を思えば雲泥の差です。

これなら流石に現場の人間、全員読めるし処理追えるでしょ……

まぁ、一部setTimeoutみたいなのはPromise使わないと上手く書けないので
そういう所は詳しい人がライブラリ化して別ファイルにしちゃいましょう。
そこだけ紹介して終わります。

js

1// setTimeoutを改造したsleep関数つくるよ 2const sleep = ms => new Promise(resolve => 3 setTimeout(resolve, ms) 4); 5 6const main = async () => { 7 console.log(1); 8 9 // 3秒待ちたい 10 await sleep(3000); 11 12 console.log(2); 13};

投稿2021/10/05 19:05

miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

PePePeT_i_C

2021/10/06 00:58

ご丁寧な解説ありがとうございます! 質問の投稿で「どちらも似たような処理になる認識ですが」と記載しましたが、 制御構文がすり抜けてしまうことを完全に失念していました。。。 歴史的経緯からお話頂けて分かりやすく勉強になり、文章としてもとても面白かったです!! ありがとうございました????????
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問