JS 非同期コードユニットテストの魔法 Promise

JS 非同期コードユニットテストの魔法 Promise

序文

この記事を書いた理由は、ユニット テストを書くときに、次のテストを実行したからです。

新しい Promise((resolve, deny) => deny(1)).then().catch(err => {
    コンソール.log(エラー)
})
非同期関数jestTest(){
    Promise.resolve().then() を待つ
    console.log('この時点で、catch が呼び出され、ログが出力されていると予想されます')
}
jestテスト()

イベント ループで catch が呼び出されるまで await を使用してテスト コードをブロックすることはできません。これにより、catch の実行が検出され、テストが合格します。

「魔法」という言葉が使われているのは、promsie のチェーン呼び出しには確かに多くのデフォルト ハンドラーと暗黙的な値の転送があるためです。

プロミスチェーン

時間を無駄にしないために、例を見てみましょう。

Promise.resolve('promise1')
.then(res => {
    console.log('promise1-1 then')
})
.then(res => {
    console.log('promise1-2 then')
})
.then(res => {
    console.log('promise1-3 then')
})
.then(res => {
    console.log('promise1-4 then')
})


Promise.resolve('promise2')
.then(res => {
    console.log('promise2-1 then')
    新しいエラーをスローします('モックエラー1')
})
.then(res => {
    console.log('promise2-2 then')
    新しいエラーをスローします('モックエラー2')
})
.catch(エラー => {
    コンソール.log(エラー)
})

上記のコード出力シーケンスに対する回答が次のものと同じ場合は、この記事をスキップできます。

約束1-1

約束2-1

約束1-2

約束1-3

エラー: モックエラー 1

約束1-4

まず前提として、これら 2 つのプロミスの then 呼び出しが交差して積み重ねられていることが既にわかっている必要があります (出力の最初の 3 行からわかるように)。この部分についてよくわからない場合は、イベント ループに関する関連記事を参照してください。同時に、記事で指定されているバージョンでは、Chrome と Nodejs のイベント ループ メカニズムはすでに同じであることに注意してください。

MDN エラー

catch の元の(私が変更した)MDN の説明を見てみましょう。

基本的に、例外が発生すると Promise チェーンは停止し、代わりにチェーン内で catch ハンドラーを探します。

例外が発生するとチェーン呼び出しは停止し、チェーン内で catch ステートメントが見つかり、実行されます。

私の最初の誤解も同じでした。catch は最初にスローされた Error を直接キャッチする、つまり、Error は promise1-2 の後に出力され、promise2-2 が配置されている then はコール スタックに追加されないと誤解していました。

しかし、実際の出力結果を観察すると、そうではないことがわかります。これは、MDN の説明の文字通りの意味が間違っていることを示しています。チェーン呼び出しは停止せず、私たちが見ていない何かを実行しました。

連鎖デフォルト処理

この時点では、デフォルトの処理を知る必要があり、MDN の説明も直接引用します。

then が呼び出される Promise が、then にハンドラーがない状態 (履行または拒否) を採用する場合、then が呼び出された元の Promise の最終状態を単純に採用して、追加のハンドラーなしで新しい Promise が作成されます。

プロミスの then に対応する状態処理のコールバックがない場合、then はこのプロミスの状態を受け入れるプロミスを自動的に生成します。つまり、then は同じ状態参照を持つプロミスを返し、それを後続の呼び出しに渡します。

上記のコードの2番目のpromise部分は次のようになります。

Promise.resolve('promise2')
.then(res => {
    console.log('promise2-1 then')
    新しいエラーをスローします('モックエラー1')
})
.then(res => {
    console.log('promise2-2 then')
    新しいエラーをスローします('モックエラー2')
// onRejected に注意してください
}, (エラー) => {
    Promise.reject(err) を返します。
})
.catch(エラー => {
    コンソール.log(エラー)
})

つまり、出力結果の promise1-2 と promise1-3 の間で promise2-2 の then が実行されるため、チェーン呼び出しは直接停止されず、promise2-2 の then が呼び出しスタックに追加されたままになります。キャッチは、直接キャッチされる最初の then によってスローされたエラーではなく、非表示の onRejected によって返される同じステータスの約束です。

略語

同様に、catch(onRejected) は then(undefined, onRejected) の省略形であることを知っておく必要があります。つまり、呼び出しチェーンの前の呼び出しでエラーがなくても、catch は呼び出しスタックを直接スキップするのではなく、呼び出しスタックに入ります。

Promise.resolve('promise1')
.then(res => {
    console.log('promise1-1 then')
})
.then(res => {
    console.log('promise1-2 then')
})
.then(res => {
    console.log('promise1-3 then')
})


Promise.resolve('promise2')
.then(res => {
    console.log('promise2-1 then')
})
.catch(エラー => {
    コンソール.log(エラー)
})
.then(res => {
    console.log('実際はpromise2-3です')
})

非同期待機

最初に注意すべきことは、この記事で指定されている NodeJs および Chrome バージョンでは、f(await promise) は promise.then(f) と完全に同等であるということです。

もちろん、Promise について議論するときに、async await を無視することはできません。プロミスの状態が onResolve のときは両者の処理ロジックは同じですが、エラー処理の実行ロジックが異なります。async await でエラーが発生すると、後続の await の実行が本当に直接スキップされます。

const promiseReject = new Promise((resolve, deny) => {
    拒否(新しいエラー('error'))
})
const promiseResolve1 = 新しい Promise((resolve, 拒否) => {
    解決('正しい')
})
const promiseResolve2 = 新しい Promise((resolve, 拒否) => {
    解決('正しい')
})
const promiseResolve3 = 新しい Promise((resolve, 拒否) => {
    解決('正しい')
})
関数demo1() {
    約束拒否
    .then(() => {
        コンソールログ('1-1')
    })
    .catch(エラー => {
        コンソールログ('1-2')
    })
}

非同期関数demo2(){
    試す {
        待つ約束拒否
        約束を待つ解決1
        promiseResolve2を待つ
        約束を待つResolve3
    } キャッチ(エラー){
        コンソールログ('2-1')
    }
}
// 2-1
// 1-2

上記は、JS 非同期コード単体テストの魔法の約束の詳細です。JS 非同期コードの約束の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • js Promise同時制御メソッド
  • JavaScriptはPromiseを使用して複数の繰り返しリクエストを処理します
  • JavaScript で Promise を使用して同時リクエスト数を制御する方法
  • JS 9 Promise 面接の質問
  • JS の Promise に中止関数を追加する方法
  • JS 非同期スタック トレース: await が Promise よりも優れている理由
  • Javascript 非同期プログラミング: Promise を本当に理解していますか?
  • JavaScript PromiseとAsync/Awaitの詳細な説明
  • フロントエンドJavaScriptの約束

<<:  MySQL に絵文字を保存するときに表示されるエラー メッセージ「java.sql.SQLException: 文字列値が正しくありません:'\xF0\x9F\x92\xA9\x0D\x0A...'」の解決方法

>>:  VMware で Centos7 ブリッジ ネットワークを構成する手順の詳細な説明

推薦する

CSS で水平方向と垂直方向に中央揃えする 10 の方法を教えます (要約)

面接には必需品、仕事でも必ず使います。うーん、誰でも分かるでしょう。これ以上何も言わずに、要約とレン...

シンプルな商品スクリーニング機能を実現するjs

この記事の例では、商品スクリーニング機能を実装するためのjsの具体的なコードを参考までに共有していま...

Vue で Excel インポート機能を実装する詳細な手順

1. フロントエンド主導の実装手順最初のステップは、ページのインポートボタンをクリックしてExcel...

VUEは登録とログインの効果を実現します

この記事の例では、登録とログインの効果を実現するためのVUEの具体的なコードを紹介します。具体的な内...

CSS BEM 記述標準の詳細な説明

BEM は、Web 開発に対するコンポーネントベースのアプローチです。ユーザー インターフェイスを独...

JS ES 新機能テンプレート文字列

目次1. テンプレート文字列とは何ですか? 2. 複数行のテンプレート文字列2.1 式付きテンプレー...

js はマウスインとマウスアウトによるカード切り替えコンテンツを実装します

この記事では、マウスでカード内外のコンテンツを切り替えるためのjsの具体的なコードを紹介します。具体...

Mysql 更新マルチテーブル共同更新方法の概要

次に、2 つのテーブルを作成し、一連の SQL 文を実行します。SQL 文の実行後にテーブル内のデー...

MySQL カーソル関数と使用法

目次意味カーソルの役割カーソルの使用カーソルの宣言カーソルを開くカーソルデータのトラバースカーソルを...

Linux システムの .bash_profile ファイルの詳細な説明

目次1. 環境変数$PATH: 2. 環境変数を変更します。 3. bash_profileの目的要...

Vue: メモリリークの詳細な説明

メモリリークとは何ですか?メモリ リークとは、新しいメモリが作成されたが、解放またはガベージ コレ...

MySQL サービス 1067 エラーの解決策: mysql 実行可能ファイルのパスを変更する

今日、MySQLサービス1067エラー問題に遭遇しました。システムアカウントを使用するように設定して...

Linux ブートシステム方式の分析

この記事では、Linux システムを起動する方法について説明します。ご参考までに、詳細は以下の通りで...

Nginxはhttpリクエスト実装プロセス分析を処理する

Nginxはまず、設定ファイル内のどのserver{}ブロックを処理に使用するかを決定します。次のs...

CentOS7 上で KVM 仮想化プラットフォームを構築する (3 つの方法)

KVM はカーネルベースの仮想マシンの略で、Linux をハイパーバイザーに変換する Linux ...