「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > JavaScript で約束のキャンセルをマスターする

JavaScript で約束のキャンセルをマスターする

2024 年 11 月 2 日に公開
ブラウズ:592

ロザリオ・デ・キアラ作✏️

JavaScript では、Promise は非同期操作を処理するための強力なツールであり、特に UI 関連のイベントで役立ちます。これらは、すぐには利用できないかもしれないが、将来のある時点で解決される値を表します。

Promise により、開発者は API 呼び出し、ユーザー インタラクション、アニメーションなどのタスクを処理するときに、よりクリーンで管理しやすいコードを作成できるようになります (または許可される必要があります)。 Promise では、.then()、.catch()、.finally() などのメソッドを使用することで、成功とエラーのシナリオをより直感的に処理できるようになり、悪名高い「コールバック地獄」を回避できます。

この記事では、新しい (2024 年 3 月) Promise.withResolvers() メソッドを使用します。このメソッドを使用すると、新しい Promise と 2 つの関数 (1 つは Promise を解決するための関数) の 3 つを含むオブジェクトを返すことで、よりクリーンでシンプルなコードを作成できます。これは最近の更新であるため、この記事の例を実行するには最新の Node ランタイム (v>22) が必要です。

新旧の JavaScript Promise メソッドの比較

次の 2 つの機能的に同等のコード チャンクでは、Promise を解決または拒否するメソッドを割り当てる古いアプローチと新しいアプローチを比較できます。

let resolve, reject;

const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

Math.random() > 0.5 ? resolve("ok") : reject("not ok");

上記のコードでは、Promise の最も伝統的な使用方法がわかります。新しい Promise オブジェクトをインスタンス化し、コンストラクターで、resolve と拒否の 2 つの関数を割り当てる必要があります。これらの関数は、次のときに呼び出されます。必要です。

次のコード スニペットでは、同じコード部分が新しい Promise.withResolvers() メソッドで書き直されており、より単純になっています。

const { promise, resolve, reject } = Promise.withResolvers();

Math.random() > 0.5 ? resolve("ok") : reject("not ok");

ここでは、新しいアプローチがどのように機能するかを確認できます。これは Promise を返します。これに対して .then() メソッドと 2 つの関数、resolve および拒否を呼び出すことができます。

Promise への従来のアプローチでは、作成ロジックとイベント処理ロジックが 1 つの関数内にカプセル化されているため、複数の条件やコードの異なる部分で Promise を解決または拒否する必要がある場合に制限が生じる可能性があります。

対照的に、Promise.withResolvers() は、Promise の作成を解決ロジックから分離することで柔軟性が向上し、複雑な条件や複数のイベントの管理に適しています。ただし、単純なユースケースの場合は、標準的な Promise パターンに慣れている人にとっては、従来の方法の方がシンプルで馴染みやすいかもしれません。

実際の例: API の呼び出し

より現実的な例で新しいアプローチをテストできるようになりました。以下のコードでは、API 呼び出しの簡単な例を確認できます:

function fetchData(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => {
                // Check if the response is okay (status 200-299)
                if (response.ok) {
                    return response.json(); // Parse JSON if response is okay
                } else {
                    // Reject the promise if the response is not okay
                    reject(new Error('API Invocation failed'));
                }
            })
            .then(data => {
                // Resolve the promise with the data
                resolve(data);
            })
            .catch(error => {
                // Catch and reject the promise if there is a network error
                reject(error);
            });
    });
}

// Example usage
const apiURL = '';

fetchData(apiURL)
    .then(data => {
        // Handle the resolved data
        console.log('Data received:', data);
    })
    .catch(error => {
        // Handle any errors that occurred
        console.error('Error occurred:', error);
    });

fetchData 関数は、URL を取得し、フェッチ API を使用して API 呼び出しを処理する Promise を返すように設計されています。応答ステータスが 200 ~ 299 の範囲内にあるかどうかを確認して応答を処理し、成功を示します。

成功した場合、応答は JSON として解析され、結果のデータで Promise が解決されます。応答が成功しなかった場合、Promise は拒否され、適切なエラー メッセージが表示されます。さらに、この関数にはネットワーク エラーを検出するためのエラー処理が含まれており、そのようなエラーが発生した場合は Promise を拒否します。

この例では、この関数の使用方法を示し、解決されたデータを .then() ブロックで管理し、エラーを .catch() ブロックを使用して処理して、成功したデータ取得とエラーの両方が適切に管理されるようにする方法を示しています。

以下のコードでは、新しい Promise.withResolvers() メソッドを使用して fetchData() 関数を書き直します。

function fetchData(url) {
    const { promise, resolve, reject } = Promise.withResolvers();

    fetch(url)
        .then(response => {
            // Check if the response is okay (status 200-299)
            if (response.ok) {
                return response.json(); // Parse JSON if response is okay
            } else {
                // Reject the promise if the response is not okay
                reject(new Error('API Invocation failed'));
            }
        })
        .then(data => {
            // Resolve the promise with the data
            resolve(data);
        })
        .catch(error => {
            // Catch and reject the promise if there is a network error
            reject(error);
        });

    return promise;
}

ご覧のとおり、上記のコードはより読みやすく、オブジェクト Promise の役割は明確です。fetchData 関数は、成功するか失敗する Promise を返し、それぞれの場合に適切なメソッドを呼び出します。 。上記のコードは、api.invocation.{old|new}.js.

という名前のリポジトリにあります。

約束のキャンセル

次のコードは、Promise キャンセル メソッドを実装する方法を検討します。ご存知かもしれませんが、JavaScript では Promise をキャンセルできません。 Promise は非同期操作の結果を表し、作成後に解決または拒否するように設計されており、キャンセルする組み込みメカニズムはありません。

この制限は、Promise に定義された状態遷移プロセスがあるために発生します。それらは保留中として開始され、一旦解決されると状態を変更できません。これらは、操作自体を制御するのではなく、操作の結果をカプセル化することを目的としています。つまり、基礎となるプロセスに影響を与えたり、キャンセルしたりすることはできません。この設計選択により、Promise はシンプルになり、操作の最終的な結果を表すことに重点が置かれます:

const cancellablePromise = () => {
    const { promise, resolve, reject } = Promise.withResolvers();

    promise.cancel = () => {
        reject("the promise got cancelled");
    };
    return promise;
};

上記のコードでは、cancellablePromise という名前のオブジェクトが表示されます。これは、ご覧のとおり、単に拒否メソッドの呼び出しを強制する追加の cancel() メソッドを備えた Promise です。これは単なる構文上の砂糖であり、JavaScript Promise をキャンセルするものではありませんが、より明確なコードを記述するのに役立つ場合があります。

別のアプローチは、AbortController と AbortSignal を使用することです。これらは、基礎となる操作 (HTTP リクエストなど) に関連付けて、必要に応じて操作をキャンセルできます。ドキュメントから、AbortController と AbortSignal のアプローチは、上記のコードで実装したものをより表現的に実装したものであることがわかります。AbortSignal が呼び出されると、Promise は拒否されるだけです。

もう 1 つのアプローチは、RxJS のようなリアクティブ プログラミング ライブラリを使用することです。これは、Observable パターンの実装、キャンセル機能を含む非同期データ ストリームのより高度な制御を提供します。

Observable と Promise の比較

実際の使用例について言えば、Promise は API からのデータのフェッチなどの単一の非同期操作の処理に適しています。対照的に、Observable は、ユーザー入力、WebSocket イベント、HTTP 応答など、時間の経過とともに複数の値が出力される可能性があるデータ ストリームの管理に最適です。

Promise は一度開始するとキャンセルできないのに対し、Observable ではストリームの登録を解除することでキャンセルできることはすでに明らかにしました。一般的な考え方は、Observable を使用すると、オブジェクトとの可能な対話の明示的な構造があるということです。

  • Observable を作成すると、すべての Observable がそれをサブスクライブできるようになります
  • Observable はその作業を実行し、状態を変更し、イベントを発行します。すべてのオブザーバーがアップデートを受け取ります。これが Promise との主な違いです。 Promise は 1 回だけ解決できますが、Observable はオブザーバーが存在する限りイベントを発行し続けることができます
  • オブザーバーが Observable からのイベントに興味がなくなったら、サブスクライブを解除してリソースを解放できます

これは以下のコードで示されています:

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

const observer = observable.subscribe({
  next(x) { console.log('Received value:', x); },
  complete() { console.log('Observable completed'); }
});

observer.unsubscribe();

Observable は 3 つの値を返すのに対し、Promise は 1 回しか解決できないため、このコードを Promise で書き直すことはできません。

unsubscribe メソッドをさらに実験するために、takewhile() メソッドを使用する別のオブザーバーを追加できます。これにより、値が特定の条件に一致するまでオブザーバーが待機します。たとえば、以下のコードでは、値が 2:
ではない間、Observable からイベントを受信し続けます。

import { Observable, takeWhile } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

const observer1 = observable.subscribe({
  next(x) { console.log('Received by 1 value:', x); },
  complete() { console.log('Observable 1 completed'); }
});

const observer2 = observable.pipe(
  takeWhile(value => value != "2")
).subscribe(value => console.log('Received by 2 value:', value));

上記のコードでは、observer1 はすでに見たものと同じです。単にサブスクライブして、Observable からすべてのイベントを受信し続けるだけです。 2 番目の Observer2 は、条件が一致している間、Observable から要素を受け取ります。この場合、値が2以外の場合を意味します。

実行すると、2 つの異なるメカニズムがどのように機能するかがわかります:

$ node observable.mjs
Received by 1 value: 1
Received by 1 value: 2
Received by 1 value: 3
Observable 1 completed
Received by 2 value: 1
$

結論

この記事では、JavaScript で Promise を割り当てる新しいメカニズムを調査し、完了前に Promise をキャンセルする可能な方法をいくつか示しました。また、Promise を Observable オブジェクトと比較しました。Observable オブジェクトは、Promise の機能を提供するだけでなく、イベントの複数の発行と購読解除のための適切なメカニズムを許可することで Promise を拡張します。


LogRocket: コンテキストを理解することで JavaScript エラーをより簡単にデバッグします

コードのデバッグは常に面倒な作業です。しかし、エラーを理解すればするほど、修正が容易になります。

LogRocket を使用すると、これらのエラーを新しい独自の方法で理解できます。当社のフロントエンド監視ソリューションは、JavaScript フロントエンドに対するユーザーの関与を追跡し、エラーを引き起こしたユーザーの行動を正確に確認できるようにします。

Mastering promise cancellation in JavaScript

LogRocket は、コンソール ログ、ページの読み込み時間、スタック トレース、ヘッダー本体を含む遅いネットワーク リクエスト/レスポンス、ブラウザーのメタデータ、カスタム ログを記録します。 JavaScript コードの影響を理解するのがこれまでになく簡単になります!

無料でお試しください。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/logrocket/mastering-promise-cancellation-in-javascript-1eb7?1 侵害がある場合は、[email protected] に連絡して削除してください。
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3