1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[JavaScript]個人的によく見かける間違った非同期処理の書き方

Last updated at Posted at 2025-11-23

はじめに

SalesforceのLWC開発をしてると、必ずと言っていいほど変な非同期処理の書き方をしてる人がいるので、まとめてみた。
「いやいやこんなの自分の周りじゃ見ないよ」という人は環境に恵まれているのだと思う。
筆者もそこまでJSに明るいわけではないので、あくまで初歩的な話しかしない。

その1 Promise地獄

名前の通り。

Promise地獄という名称が一般的なのか知らないが、要はコールバック地獄がそのままthenで再現されたような、多重ネスト式になっている実装。
一応正常な実装と同じ挙動を取るのが逆に質が悪い。
コードレビューの段階でレビュアーもJSに疎い人がやってると後続のテスト工程とかで検知されずにそのまま本番まで行ってしまい、気付いた時には「既に本番で動いてるのでなるべく変えたくない」と修正が困難に。
結果後からプロジェクトに参画した者はこの読みにくいコードを必死こいて解読する苦行が最初の仕事となる。
あとこの書き方だと通常のPromiseチェーンと異なり必ずcatchを各thenごとに用意しなければならないが、これをやる人はたいていそんなこと知ったこっちゃないとcatchが足りてないか、最悪そもそもcatch自体を書いていないことも多い。
その場合、正常系はともかく当然異常系でボロボロNGが出まくる(ちゃんと異常系もテストしてればだが......)。

Bad Practice
async function asyncFunc1() {
    return 1;
}

async function asyncFunc2(arg1) {
    return arg1 + 1;
}

async function asyncFunc3(arg2) {
    return arg2 + 1;
}

asyncFunc1().then((result1) => {
    console.log(result1); // 1
    asyncFunc2(result1).then((result2) => {
        console.log(result2); // 2
        asyncFunc3(result2).then((result3) => {
            console.log(result3); // 3
        }).catch((error3) => {
            console.error(error3);
        });
    }).catch((error2) => {
        console.error(error2);
    });
}).catch((error1) => {
    console.error(error1);
});

上記の例を一般的なPromiseチェーンの書き方に直すと以下のようになる。
ネストが解消され、catchの数も減り、パッと見で読みやすくなったのではないだろうか。

Promiseチェーンの名前通り、前の非同期関数の返すPromiseをthenで解決、関数の戻り値をthen内で編集後returnし、それを次の非同期関数に渡している。
このように非同期関数を連鎖させて使うのが本来のthen-catchの使い方である。
またこちらの書き方の場合、catchは基本1つだけでよく、この単一のcatchがチェーンされた非同期関数すべての例外を捕捉してくれる(複数用意してやることも可能だが、基本は不要だろう)。

Good Practice
async function asyncFunc1() {
    return 1;
}

async function asyncFunc2(arg1) {
    return arg1 + 1;
}

async function asyncFunc3(arg2) {
    return arg2 + 1;
}

asyncFunc1().then((result1) => {
    console.log(result1); // 1
    return result1;
    
}).then(asyncFunc2).then((result2) => {
    console.log(result2); // 2
    return result2;
    
}).then(asyncFunc3).then((result3) => {
    console.log(result3); // 3
    
}).catch((error) => {
    console.error(error);
});

その2 cacthのないPromiseチェーン

これも名前通り。
Promiseチェーン使うならcatchはマスト。
実装してるのが共通関数とかで、呼び出し元に例外を投げたいとかならcatchの中でthrowするべき。

Bad Practice
async function asyncFunc1() {
    return 1;
}

async function asyncFunc2(arg1) {
    return arg1 + 1;
}

async function asyncFunc3(arg2) {
    return arg2 + 1;
}

asyncFunc1().then((result1) => {
    console.log(result1); // 1
    return result1;
    
}).then(asyncFunc2).then((result2) => {
    console.log(result2); // 2
    return result2;
    
}).then(asyncFunc3).then((result3) => {
    console.log(result3); // 3
});

Good Practiceはその1と同じ(必要に応じてcatchの中でthrowなりする)。

その3 コールバック地獄

setTimeoutなどのコールバック関数を逐次実行したいときに、コールバック地獄を書く人がいるが、new Promiseを使えば綺麗に書ける。

Bad Practice
function callbackHell() {

    setTimeout(() => {
        const result1 = 1;
        console.log(initialData);
        
        setTimeout(() => {
            const result2 = result1 + 1;
            console.log(result2);
            
            setTimeout(() => {
                const result3 = result2 + 1;
                console.log(result3);
                
            }, 500); 
        }, 1000); 
    }, 1500); 
}

callbackHell();

自分の場合はよく使うコールバック関数(大体setTimeout)をこんな感じにnew Promiseでラップして返す共通関数を作り、それを使いまわすようにするが、そこら辺は好みか。

Good Practice
function delay(seconds) {
    return new Promise((resolve, reject) => setTimeout(resolve, seconds));
}

async function cleanedFunc() {

    try {
        await delay(1500);
        const result1 = 1;
        console.log(initialData); // 1
    
        await delay(1000);
        const result2 = result1 + 1;
        console.log(result2); // 2
    
        await delay(500);
        const result3 = result2 + 1;
        console.log(result3); // 3
        
    } catch (error) {
        console.error(error);
    }
}

cleanedFunc();

まとめ

以上、自分が良く出くわす非同期処理の良くない(と思っている)実装例となる。
非同期処理はバグってる実装(実行順が制御されていないとか)でも、テストデータや環境によっては一見うまく動いて見えたりして、テストだけではなかなかバグが気付けないことも多い。
やはりコーディングの段階でバグを減らすのが最も効率的なので、そのためにも可読性には注意したい。

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?