Promiseオブジェクト

約束の意味

Promise は、従来のソリューション (コールバックやイベント) よりも合理的かつ強力な非同期プログラミングのソリューションです。これは最初にコミュニティによって提案および実装され、ES6 が言語標準に組み込み、その使用法を統一し、「Promise」オブジェクトをネイティブに提供しました。

いわゆる「Promise」は、将来終了するイベント (通常は非同期操作) の結果を保存する単なるコンテナです。構文的に言えば、Promise は非同期操作のメッセージを取得できるオブジェクトです。 Promise は統一された API を提供しており、さまざまな非同期操作を同様に処理できます。

「Promise」オブジェクトは以下の2つの特徴を持っています。

(1) 物体の状態は外界の影響を受けない。 「Promise」オブジェクトは非同期操作を表し、「pending」(進行中)、「fulfilled」(成功)、および「rejected」(失敗)の 3 つの状態があります。非同期操作の結果のみが現在の状態を決定でき、他の操作はこの状態を変更できません。これが「約束」という名前の由来でもあり、英語で「約束」という意味で、他の手段では変えられないという意味です。

(2) 一度変化した状態は再度変化せず、いつでもこの結果が得られます。 「Promise」オブジェクトの状態が変化する可能性は 2 つだけです。「保留中」から「履行」へ、および「保留中」から「拒否」へです。この 2 つの状況が発生する限り、状態は固定され、それ以上変化せず、この結果が維持されます。これを解決済みと呼びます。変更がすでに行われている場合は、Promise オブジェクトにコールバック関数を追加すると、結果がすぐに得られます。イベントとは全く違いますが、聞き逃してまた聞き直すと結果が出ないのがイベントの特徴です。

記述の便宜上、この章の後半で「解決済み」は「履行」状態のみを指し、「拒否」状態は含まれないことに注意してください。

「Promise」オブジェクトを使用すると、入れ子になったコールバック関数の層を回避して、非同期操作を同期操作プロセスとして表現できます。さらに、「Promise」オブジェクトは統一されたインターフェイスを提供するため、非同期操作の制御が容易になります。

「Promise」にはいくつかの欠点もあります。まず、「Promise」は一度作成するとすぐに実行され、途中でキャンセルすることはできません。次に、コールバック関数を設定しない場合、Promise によって内部的にスローされたエラーは外部には反映されません。第三に、「保留中」状態では、現在の進行状況がどの段階にあるのか (開始したばかりか、完了しようとしているか) を知ることができません。

特定のイベントが繰り返し発生する場合、一般的に、「Promise」をデプロイするよりも Stream パターンを使用する方が良い選択です。

基本的な使い方

ES6 では、「Promise」オブジェクトが「Promise」インスタンスの生成に使用されるコンストラクターであると規定されています。

次のコードは Promise インスタンスを作成します。

const Promise = new Promise(function(resolve,拒否) {
  // ... いくつかのコード

  if (/* 非同期操作が成功しました */){
    解決(値);
  } それ以外 {
    拒否(エラー);
  }
});

Promise コンストラクターは関数をパラメーターとして受け取ります。関数の 2 つのパラメーターは resolvereject です。これらは JavaScript エンジンによって提供される 2 つの関数であり、自分でデプロイする必要はありません。

resolve 関数の機能は、Promise オブジェクトのステータスを「未完了」から「成功」に変更することです (つまり、保留中から解決済みに)。これは、非同期操作が成功したときに呼び出され、結果を受け取ります。非同期操作の値をパラメータとして渡します。reject 関数の機能は、Promise オブジェクトのステータスを「未完了」から「失敗」に変更することです。非同期操作が失敗したときに呼び出され、エラーがパラメーターとして渡されます。

「Promise」インスタンスが生成された後、「then」メソッドを使用して、「解決」状態と「拒否」状態のコールバック関数をそれぞれ指定できます。

promise.then(関数(値) {
  //成功
}、関数(エラー) {
  // 失敗
});

「then」メソッドは 2 つのコールバック関数をパラメータとして受け入れることができます。最初のコールバック関数は、Promise オブジェクトの状態が resolved に変化したときに呼び出され、2 番目のコールバック関数は Promise オブジェクトの状態が rejected に変化したときに呼び出されます。どちらの関数もオプションであり、必ず指定する必要はありません。これらはすべて、「Promise」オブジェクトによってパラメータとして渡される値を受け入れます。

以下は「Promise」オブジェクトの簡単な例です。

関数タイムアウト(ミリ秒) {
  return new Promise((解決、拒否) => {
    setTimeout(resolve, ms, 'done');
  });
}

タイムアウト(100).then(() => {
  console.log(値);
});

上記のコードでは、timeout メソッドは、一定期間後に発生する結果を示す Promise インスタンスを返します。指定された時間 (ms パラメータ) が経過すると、Promise インスタンスの状態は resolved に変わり、then メソッドにバインドされたコールバック関数がトリガーされます。

Promise は作成後すぐに実行されます。

letpromise = new Promise(function(resolve,拒否) {
  console.log('約束');
  解決する();
});

プロミス.then(関数() {
  console.log('解決されました。');
});

console.log('こんにちは!');

// 約束
// こんにちは!
// 解決されました

上記のコードでは、Promise は作成直後に実行されるため、最初の出力は Promise になります。そして、現在のスクリプトのすべての同期タスクが実行されるまで、then メソッドで指定されたコールバック関数は実行されないため、最後に resolved が出力されます。

以下は画像を非同期で読み込む例です。

関数loadImageAsync(url) {
  return new Promise(function(resolve,拒否) {
    const image = 新しい画像();

    image.onload = function() {
      解決(画像);
    };

    image.onerror = function() {
      拒否(新しいエラー('画像を' + URLに読み込めませんでした));
    };

    画像.src = URL;
  });
}

上記のコードでは、「Promise」を使用して画像読み込みの非同期操作をラップしています。ロードが成功すると resolve メソッドが呼び出され、そうでない場合は reject メソッドが呼び出されます。

以下は、「Promise」オブジェクトを使用して実装された Ajax 操作の例です。

const getJSON = 関数(url) {
  constpromise = new Promise(function(resolve,reject){
    const ハンドラー = function() {
      if (this.readyState !== 4) {
        戻る;
      }
      if (this.status === 200) {
        解決(this.response);
      } それ以外 {
        拒否(新しいエラー(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", URL);
    client.onreadystatechange = ハンドラー;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  返却の約束。
};

getJSON("/posts.json").then(function(json) {
  console.log('コンテンツ: ' + json);
}、関数(エラー) {
  console.error('何か問題が発生しました', エラー);
});

上記のコードでは、getJSON は XMLHttpRequest オブジェクトのカプセル化であり、JSON データに対する HTTP リクエストを発行し、Promise オブジェクトを返すために使用されます。 getJSON 内では、resolve 関数と reject 関数がパラメータ付きで呼び出されることに注意してください。

「resolve」関数と「reject」関数がパラメータを指定して呼び出された場合、それらのパラメータはコールバック関数に渡されます。通常、「reject」関数のパラメータは、スローされたエラーを表す「Error」オブジェクトのインスタンスです。また、「resolve」関数のパラメータは、次のような別の Promise インスタンスである場合もあります。 。

const p1 = new Promise(function (解決、拒否) {
  // ...
});

const p2 = new Promise(function (解決、拒否) {
  // ...
  解決(p1);
})

上記のコードでは、p1p2 は両方とも Promise のインスタンスですが、p2resolve メソッドはパラメータとして p1 を受け取ります。つまり、1 つの非同期操作の結果は別の非同期操作を返します。手術。

このとき、「p1」の状態は「p2」に渡されます。つまり、「p1」の状態が「p2」の状態を決定することに注意してください。 p1 のステータスが pending の場合、p2 のコールバック関数は p1 の状態が変化するのを待ちます。p1 の状態がすでに resolved または rejected である場合は、 p2 のコールバック関数 この関数はすぐに実行されます。

const p1 = new Promise(function (解決、拒否) {
  setTimeout(() =>拒否(新しいエラー('失敗')), 3000)
})

const p2 = new Promise(function (解決、拒否) {
  setTimeout(() => 解決(p1), 1000)
})

p2
  .then(結果 => console.log(結果))
  .catch(エラー => console.log(エラー))
// エラー: 失敗します

上記のコードでは、「p1」は Promise であり、3 秒後に「拒否」されます。 p2 のステータスは 1 秒後に変化し、resolve メソッドは p1 を返します。 'p2' は別の Promise を返すため、'p2' 自身の状態は無効であり、'p2' の状態は 'p1' の状態によって決まります。したがって、後続の「then」ステートメントは後者 (「p1」) を対象とします。さらに 2 秒後、「p1」は「拒否」になり、「catch」メソッドで指定されたコールバック関数がトリガーされます。

「resolve」または「reject」を呼び出しても、Promise のパラメータ関数の実行は終了しないことに注意してください。

new Promise((解決、拒否) => {
  解決(1);
  コンソール.ログ(2);
}).then(r => {
  コンソール.ログ(r);
});
// 2
// 1

上記のコードでは、「resolve(1)」を呼び出した後も、後続の「console.log(2)」が実行され、最初に出力されます。これは、すぐに解決された Promise が現在のイベント ループの最後に、常に現在のサイクルの同期タスクよりも後に実行されるためです。

一般に、「resolve」または「reject」を呼び出した後、Promise のミッションは完了します。その後の操作は「then」メソッド内に配置する必要があり、「resolve」または「reject」の直後に記述する必要はありません。したがって、予期せぬことがないよう、「return」ステートメントを先頭に置くことが最善です。

new Promise((解決、拒否) => {
  戻り解決(1);
  //以下のステートメントは実行されません
  コンソール.ログ(2);
})

Promise.prototype.then()

Promise インスタンスには「then」メソッドがあります。つまり、「then」メソッドはプロトタイプ オブジェクト「Promise.prototype」で定義されます。その機能は、状態が変化したときに Promise インスタンスにコールバック関数を追加することです。前に述べたように、「then」メソッドの最初のパラメータは「解決」状態のコールバック関数であり、2 番目のパラメータは「拒否」状態のコールバック関数です。これらは両方ともオプションです。

then メソッドは、新しい Promise インスタンスを返します (元の Promise インスタンスではないことに注意してください)。したがって、連鎖書き込みメソッドを使用できます。つまり、「then」メソッドの後に別の「then」メソッドが続きます。

getJSON("/posts.json").then(function(json) {
  json.post を返します。
}).then(関数(ポスト) {
  // ...
});

上記のコードは、「then」メソッドを使用して 2 つのコールバック関数を順番に指定します。最初のコールバック関数が完了すると、戻り結果がパラメーターとして 2 番目のコールバック関数に渡されます。

連鎖した「then」を使用すると、順番に呼び出される一連のコールバック関数を指定できます。この時点で、前のコールバック関数は依然として Promise オブジェクトを返す可能性があります (つまり、非同期操作が存在します)。このとき、後者のコールバック関数は、Promise オブジェクトの状態が変化するまで待機します。と呼ばれた。

getJSON("/post/1.json").then(function(post) {
  getJSON(post.commentURL)を返します;
}).then(関数 (コメント) {
  console.log("解決済み: ", コメント);
}、関数 (エラー){
  console.log("拒否されました: ", err);
});

上記のコードでは、最初の then メソッドで指定されたコールバック関数は別の Promise オブジェクトを返します。このとき、2 番目の then メソッドで指定されたコールバック関数は、この新しい Promise オブジェクトの状態が変化するのを待ちます。 「解決済み」になると、最初のコールバック関数が呼び出されます。ステータスが「拒否」になると、2 番目のコールバック関数が呼び出されます。

アロー関数を使用すると、上記のコードをより簡潔に記述することができます。

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
)。それから(
  コメント => console.log("解決済み: ", コメント),
  err => console.log("拒否されました: ", err)
);

Promise.prototype.catch()

Promise.prototype.catch() メソッドは、エラーが発生した場合のコールバック関数を指定するために使用される .then(null, requestion) または .then(unknown, rejection) のエイリアスです。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(関数(エラー) {
  // getJSON と、前のコールバック関数の実行時に発生したエラーを処理します
  console.log('エラーが発生しました!', エラー);
});

上記のコードでは、「getJSON()」メソッドは Promise オブジェクトを返します。オブジェクトのステータスが「解決済み」に変わると、非同期操作でエラーがスローされた場合は、「then()」メソッドで指定されたコールバック関数が呼び出されます。 rejected の場合、このエラーを処理するために catch() メソッドで指定されたコールバック関数が呼び出されます。また、then() メソッドで指定したコールバック関数が動作中にエラーをスローした場合も、catch() メソッドによってキャッチされます。

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('拒否されました', err));

// と同等
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("拒否されました:", err));

以下に例を示します。

const Promise = new Promise(function(resolve,拒否) {
  新しいエラーをスロー('テスト');
});
プロミス.catch(関数(エラー) {
  コンソール.ログ(エラー);
});
// エラー: テスト

上記のコードでは、promise がエラーをスローします。このエラーは、catch() メソッドで指定されたコールバック関数によってキャッチされます。なお、上記の書き込み方法は、以下の2つの書き込み方法と同等である。

//書き方その1
const Promise = new Promise(function(resolve,拒否) {
  試す {
    新しいエラーをスロー('テスト');
  } キャッチ(e) {
    拒否(e);
  }
});
プロミス.catch(関数(エラー) {
  コンソール.ログ(エラー);
});

//書き方2
const Promise = new Promise(function(resolve,拒否) {
  拒否(新しいエラー('テスト'));
});
プロミス.catch(関数(エラー) {
  コンソール.ログ(エラー);
});

上記 2 つの書き方を比較すると、reject() メソッドの機能がエラーをスローすることに相当することがわかります。

Promise ステータスが「解決済み」に変わった場合、再度エラーをスローしても効果はありません。

const Promise = new Promise(function(resolve,拒否) {
  解決('ok');
  新しいエラーをスロー('テスト');
});
約束
  .then(関数(値) { console.log(値) })
  .catch(関数(エラー) { コンソール.ログ(エラー) });
//わかりました

上記のコードでは、「resolve」ステートメントの後に Promise がスローされた場合、それはキャッチされません。つまり、スローされません。 Promise の状態が一度変更されると、その状態が永久に維持され、再び変更されることはないからです。

Promise オブジェクトのエラーには「バブル」の性質があり、捕捉されるまで逆方向に伝播します。つまり、エラーは常に次の catch ステートメントによって捕捉されます。

getJSON('/post/1.json').then(function(post) {
  getJSON(post.commentURL)を返します;
}).then(関数(コメント) {
  // 何らかのコード
}).catch(関数(エラー) {
  // 最初の 3 つの Promise によって生成されたエラーを処理します
});

上記のコードには 3 つの Promise オブジェクトがあります。1 つは getJSON() によって生成され、2 つは then() によって生成されます。これらのいずれかによってスローされたエラーは、最後の catch() によってキャッチされます。

一般に、then() メソッドでは Reject 状態コールバック関数 (つまり、then の 2 番目のパラメータ) を定義しないでください。常に catch メソッドを使用してください。

// 悪い
約束
  .then(関数(データ) {
    //成功
  }、関数(エラー) {
    // エラー
  });

// 良い
約束
  .then(関数(データ) { //cb
    //成功
  })
  .catch(関数(エラー) {
    // エラー
  });

上記のコードでは、2 番目の書き方の方が最初の書き方よりも優れています。その理由は、2 番目の書き方は、前の then メソッドの実行時にエラーをキャッチでき、同期的な書き方に近いためです。 (「トライ/キャッチ」)。したがって、then() メソッドの 2 番目のパラメータを使用する代わりに、常に catch() メソッドを使用することをお勧めします。

従来の try/catch コード ブロックとは異なり、catch() メソッドを使用してエラー処理コールバック関数を指定しない場合、Promise オブジェクトによってスローされたエラーは外部コードに渡されません。 、何も起こりません。

const someAsyncThing = function() {
  return new Promise(function(resolve,拒否) {
    //x が宣言されていないため、次の行ではエラーが報告されます
    解決(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('すべてが素晴らしい');
});

setTimeout(() => { console.log(123) }, 2000);
// キャッチされません (約束どおり) ReferenceError: x が定義されていません
// 123

上記のコードでは、someAsyncThing() 関数によって生成された Promise オブジェクトに内部構文エラーがあります。ブラウザがこの行に到達すると、エラー メッセージ「ReferenceError: x が定義されていません」が出力されますが、プロセスは終了せず、スクリプトの実行も 2 秒後に出力されます。これは、Promise 内のエラーが Promise の外側のコードに影響を与えないことを意味します。よく言われるのが、「Promise はエラーを食べる」ということです。

このスクリプトはサーバー上で実行され、終了コードは「0」です (実行が成功したことを意味します)。ただし、Node.js には、特に捕捉されなかった reject エラーをリッスンする unhandledRejection イベントがあり、上記のスクリプトはこのイベントのリスニング関数をトリガーし、リスニング関数でエラーがスローされる可能性があります。

process.on('unhandledRejection', function (err, p) {
  エラーをスローします。
});

上記のコードでは、「unhandledRejection」イベントの listen 関数には 2 つのパラメーターがあります。1 つ目はエラー オブジェクトで、2 つ目はエラーを報告した Promise インスタンスであり、エラーが発生した環境情報を理解するために使用できます。 。

Node は将来「unhandledRejection」イベントを非推奨にする計画があることに注意してください。 Promise 内に捕捉されなかったエラーがある場合、プロセスは直接終了され、プロセスの終了コードは 0 ではありません。

次の例をもう一度見てください。

const Promise = new Promise(function (解決、拒否) {
  解決('ok');
  setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(関数 (値) { console.log(値) });
//わかりました
// キャッチされないエラー: テスト

上記のコードでは、Promise は「イベント ループ」の次のラウンドでエラーがスローされることを指定しています。その時点までに Promise の実行は終了しているため、このエラーは Promise 関数の本体の外にスローされ、最外層までバブルアップしてキャッチされないエラーになります。

Promise 内で発生したエラーを処理できるように、Promise オブジェクトの後に catch() メソッドを続けることが一般的に推奨されます。 catch() メソッドは引き続き Promise オブジェクトを返すため、後で then() メソッドを呼び出すことができます。

const someAsyncThing = function() {
  return new Promise(function(resolve,拒否) {
    //x が宣言されていないため、次の行ではエラーが報告されます
    解決(x + 2);
  });
};

someAsyncThing()
.catch(関数(エラー) {
  console.log('ああ、'、エラー);
})
.then(関数() {
  console.log('続けて');
});
// いや、[参照エラー: x が定義されていません]
// 続ける

上記のコードは、catch() メソッドで指定されたコールバック関数を実行した後、後続の then() メソッドで指定されたコールバック関数を実行します。エラーが報告されない場合、catch() メソッドはスキップされます。

Promise.resolve()
.catch(関数(エラー) {
  console.log('ああ、'、エラー);
})
.then(関数() {
  console.log('続けて');
});
// 続ける

エラーは報告されないため、上記のコードは catch() メソッドをスキップし、後続の then() メソッドを直接実行します。このとき、then() メソッドでエラーが報告されたとしても、それは前の catch() とは何の関係もありません。

catch() メソッドでもエラーがスローされることがあります。

const someAsyncThing = function() {
  return new Promise(function(resolve,拒否) {
    //x が宣言されていないため、次の行ではエラーが報告されます
    解決(x + 2);
  });
};

someAsyncThing().then(function() {
  someOtherAsyncThing() を返します。
}).catch(関数(エラー) {
  console.log('ああ、'、エラー);
  // y が宣言されていないため、次の行ではエラーが報告されます
  y+2;
}).then(関数() {
  console.log('続けて');
});
// いや、[参照エラー: x が定義されていません]

上記のコードでは、catch() メソッドはエラーをスローします。その背後に他の catch() メソッドがないため、エラーは捕捉されず、外側の層に渡されません。書き直すと結果は変わります。

someAsyncThing().then(function() {
  someOtherAsyncThing() を返します。
}).catch(関数(エラー) {
  console.log('ああ、'、エラー);
  // y が宣言されていないため、次の行ではエラーが報告されます
  y+2;
}).catch(関数(エラー) {
  console.log('キャリーオン', エラー);
});
// いや、[参照エラー: x が定義されていません]
// 続行 [参照エラー: y が定義されていません]

上記のコードでは、2 番目の catch() メソッドは、前の catch() メソッドによってスローされたエラーをキャッチするために使用されます。

Promise.prototype.finally()

finally() メソッドは、Promise オブジェクトの最終状態に関係なく実行される操作を指定するために使用されます。このメソッドは ES2018 で標準的に導入されました。

約束
.then(結果 => {···})
.catch(エラー => {···})
.finally(() => {···});

上記のコードでは、promiseの最終状態に関わらず、thenまたはcatchで指定されたコールバック関数を実行した後、finallyメソッドで指定されたコールバック関数が実行されます。

以下は、サーバーが Promise を使用してリクエストを処理し、その後、「finally」メソッドを使用してサーバーをシャットダウンする例です。

サーバー.リッスン(ポート)
  .then(関数() {
    // ...
  })
  .finally(サーバー.ストップ);

「finally」メソッドのコールバック関数はパラメータを受け入れません。つまり、前の Promise ステータスが「履行」か「拒否」かを知る方法がありません。これは、「finally」メソッドの操作が状態に依存せず、Promise の実行結果に依存しないことを示しています。

「finally」は本質的に「then」メソッドの特殊なケースです。

約束
.finally(() => {
  // 声明
});

// と同等
約束
。それから(
  結果 => {
    // 声明
    結果を返します。
  }、
  エラー => {
    // 声明
    エラーをスローします。
  }
);

上記のコードで、「finally」メソッドが使用されていない場合、成功と失敗のために同じステートメントを 1 回記述する必要があります。 finally メソッドを使用すると、一度記述するだけで済みます。

その実装も非常に簡単です。

Promise.prototype.finally = 関数 (コールバック) {
  P = this.constructor とします。
  これを返します。その後(
    値 => P.resolve(callback()).then(() => 値)、
    理由 => P.resolve(callback()).then(() => { 理由をスロー })
  );
};

上記のコードでは、前の Promise が「履行」か「拒否」かに関係なく、コールバック関数「callback」が実行されます。

上記の実装から、「finally」メソッドが常に元の値を返すこともわかります。

//solve の値は未定義です
Promise.resolve(2).then(() => {}, () => {})

// 解決の値は 2 です
Promise.resolve(2).finally(() => {})

//拒否の値は未定義です
Promise.reject(3).then(() => {}, () => {})

//拒否の値は3です
Promise.reject(3).finally(() => {})

Promise.all()

Promise.all() メソッドは、複数の Promise インスタンスを新しい Promise インスタンスにラップするために使用されます。

const p = Promise.all([p1, p2, p3]);

上記のコードでは、Promise.all() メソッドはパラメータとして配列を受け入れます。そうでない場合は、以下で説明する Promise.resolve が実行します。メソッドが最初に呼び出され、その後の処理のためにパラメータを Promise インスタンスに変換します。さらに、Promise.all() メソッドのパラメーターは配列である必要はありませんが、Iterator インターフェイスが必要であり、返される各メンバーは Promise インスタンスです。

pの状態はp1p2p3によって決定され、2つの状況に分けられます。

(1) p1p2p3 のステータスがすべてfulfilled になった場合にのみ、p の状態が fulfilled になります。 p3 戻り値は配列に形成され、p` のコールバック関数に渡されます。

(2) p1p2p3のいずれかがrejectedである限り、pのステータスはrejectedになります。 rejectedp のコールバック関数に渡されます。

具体的な例を示します。

// Promise オブジェクトの配列を生成します
const 約束 = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(約束).then(関数 (投稿) {
  // ...
}).catch(関数(理由){
  // ...
});

上記のコードでは、「promises」は 6 つの Promise インスタンスを含む配列であり、これら 6 つのインスタンスのステータスが「fulfilled」になった場合、またはそれらの 1 つが「rejected」になった場合にのみ、「Promise.all」メソッドが呼び出されます。関数。

別の例を示します。

const DatabasePromise = connectDatabase();

const BooksPromise = データベースPromise
  .then(findAllBooks);

const userPromise = データベースプロミス
  .then(getCurrentUser);

Promise.all([
  本の約束、
  ユーザーの約束
])
.then(([書籍, ユーザー]) => pickTopRecommendations(書籍, ユーザー));

上記のコードでは、booksPromiseuserPromise は 2 つの非同期操作であり、コールバック関数 pickTopRecommendations は、結果が返されるまでトリガーされません。

パラメータとしての Promise インスタンスに独自の catch メソッドが定義されている場合、それが rejected されると、Promise.all()catch メソッドはトリガーされないことに注意してください。

const p1 = new Promise((解決、拒否) => {
  解決('こんにちは');
})
.then(結果 => 結果)
.catch(e => e);

const p2 = new Promise((解決、拒否) => {
  throw new Error('エラーが報告されました');
})
.then(結果 => 結果)
.catch(e => e);

Promise.all([p1, p2])
.then(結果 => console.log(結果))
.catch(e => console.log(e));
// ["hello", エラー: エラーが報告されました]

上記のコードでは、「p1」が最初に「解決」され、「p2」が「拒否」されますが、「p2」には新しい Promise インスタンスを返す独自の「catch」メソッドがあり、「p2」はこれは一例です。インスタンスが catch メソッドを実行すると、そのメソッドも resolved になり、Promise.all() メソッド パラメータ内の両方のインスタンスが resolved になるため、then メソッドで指定されたコールバック関数はcatch メソッドで指定されたコールバック関数は呼び出されません。

p2 が独自の catch メソッドを持たない場合、Promise.all()catch メソッドが呼び出されます。

const p1 = new Promise((解決、拒否) => {
  解決('こんにちは');
})
.then(結果 => 結果);

const p2 = new Promise((解決、拒否) => {
  throw new Error('エラーが報告されました');
})
.then(結果 => 結果);

Promise.all([p1, p2])
.then(結果 => console.log(結果))
.catch(e => console.log(e));
// エラー: エラーが報告されました

Promise.race()

Promise.race() メソッドは、複数の Promise インスタンスを新しい Promise インスタンスにラップします。

const p = Promise.race([p1, p2, p3]);

上記のコードでは、p1p2、および p3 のうちの 1 つのインスタンスが最初に状態を変更する限り、p の状態もそれに応じて変更されます。最初に変更された Promise インスタンスの戻り値が p のコールバック関数に渡されます。

Promise.race() メソッドのパラメータは Promise.all() メソッドと同じです。Promise インスタンスでない場合は、以下に説明する Promise.resolve() メソッドが最初に呼び出されます。パラメータを Promise インスタンスに変換します。

以下は例です。指定した時間内に結果が得られなかった場合、Promise ステータスは「reject」に変更され、そうでない場合は「resolve」に変更されます。

const p = Promise.race([
  fetch('/時間がかかる可能性のあるリソース'),
  new Promise(function (解決、拒否) {
    setTimeout(() =>拒否(新しいエラー('リクエストタイムアウト')), 5000)
  })
]);

p
.then(コンソール.ログ)
.catch(コンソール.エラー);

上記のコードでは、fetch メソッドが 5 秒以内に結果を返せない場合、変数 p のステータスが rejected に変更され、catch メソッドで指定されたコールバック関数がトリガーされます。

Promise.allSettled()

場合によっては、各操作が成功したか失敗したかに関係なく、一連の非同期操作が完了するまで待ってから、次のステップに進みたいことがあります。ただし、既存の Promise メソッドではこの要件を達成することは困難です。

Promise.all() メソッドは、すべての非同期操作が成功した場合にのみ適しています。1 つの操作が失敗すると、要件を満たすことができません。

const url = [url_1, url_2, url_3];
const リクエスト = urls.map(x => fetch(x));

試す {
  Promise.all(リクエスト)を待ちます;
  console.log('すべてのリクエストは成功しました。');
} キャッチ {
  console.log('少なくとも 1 つのリクエストが失敗しました。他のリクエストはまだ終了していない可能性があります。');
}

上記の例では、Promise.all() はすべてのリクエストが成功したことを判断できますが、1 つのリクエストが失敗すると、他のリクエストが完了したかどうかに関係なく、エラーが報告されます。

この問題を解決するために、ES2020 では、非同期操作のグループが終了したかどうかを判断する Promise.allSettled() メソッドが導入されました (成功か失敗か)。したがって、その名前は「決済」となり、「満たされた」状況と「拒否された」状況の両方が含まれます。

Promise.allSettled() メソッドは配列をパラメーターとして受け取り、配列の各メンバーは Promise オブジェクトであり、新しい Promise オブジェクトを返します。パラメータ配列内のすべての Promise オブジェクトの状態が変更された場合 (「満たされた」か「拒否された」か) に限り、返された Promise オブジェクトの状態は変更されます。

const の約束 = [
  fetch('/api-1')、
  fetch('/api-2')、
  fetch('/api-3')、
];

await Promise.allSettled(約束);
RemoveLoadingIndicator();

上記の例では、配列 promises に 3 つのリクエストが含まれています。これら 3 つのリクエストが完了するまで、removeLoadingIndicator() は実行されません (リクエストが成功したか失敗したかに関係なく)。

このメソッドによって返される新しい Promise インスタンスは、ステータスが変更されると、ステータスは常に「履行」になり、「拒否」にはなりません。ステータスが満たされると、そのコールバック関数はパラメータとして配列を受け取り、配列の各メンバーは前の配列内の各 Promise オブジェクトに対応します。

const 解決済み = Promise.resolve(42);
const拒否 = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([解決済み、拒否されました]);

allSettledPromise.then(関数 (結果) {
  console.log(結果);
});
//[
// { ステータス: '満たされました'、値: 42 },
// { ステータス: '拒否'、理由: -1 }
//]

上記のコードでは、Promise.allSettled() の戻り値は allSettledPromise であり、ステータスは fulfilled のみになります。コールバック関数によって受け取られるパラメータは配列 results です。この配列の各メンバーはオブジェクトであり、Promise.allSettled() に渡される配列内の 2 つの Promise オブジェクトに対応します。

「results」の各メンバーはオブジェクトであり、オブジェクトの形式は固定されており、非同期操作の結果に対応します。

// 非同期操作が成功した場合
{ステータス: '満たされました'、値: 値}

//非同期操作が失敗した場合
{ステータス: '拒否'、理由: 理由}

メンバー オブジェクトの status 属性の値は、文字列 fulfilled または文字列 rejected のみにすることができ、非同期操作が成功したか失敗したかを区別するために使用されます。成功した場合 (fulfilled)、オブジェクトは value 属性を持ち、失敗した場合 (rejected)、前の非同期操作の戻り値に対応する reason 属性を持ちます。二つの州。

以下に戻り値の使用例を示します。

const Promise = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);

// 成功したリクエストを除外します
const successPromises = results.filter(p => p.status === 'fulfilled');

// 失敗したリクエストを除外し、理由を出力します
const エラー = 結果
  .filter(p => p.status === '拒否')
  .map(p => p.reason);

Promise.any()

ES2021 では Promise.any() メソッド が導入されています。このメソッドは、Promise インスタンスのセットをパラメータとして受け取り、それらを新しい Promise インスタンスにラップして返します。

Promise.any([
  fetch('https://v8.dev/').then(() => 'ホーム'),
  fetch('https://v8.dev/blog').then(() => 'ブログ'),
  fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => { // 1 つの fetch() リクエストが成功する限り
  console.log(最初);
}).catch((error) => { // 3 つの fetch() リクエストはすべて失敗しました
  コンソール.ログ(エラー);
});

1 つのパラメータ インスタンスが「履行」状態になると、パッケージ化インスタンスも「履行」状態になり、すべてのパラメータ インスタンスが「拒否」状態になると、パッケージ化インスタンスは「拒否」状態になります。

Promise.any()Promise.race() メソッドと非常に似ていますが、唯一の違いは、Promise がすべてのパラメーターが拒否されるまで待機する必要があるため、Promise.any() は終了しないことです。約束は「拒否」されるまで終わらない。

以下は、「Promise()」を「await」コマンドで使用する例です。

const の約束 = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];

試す {
  const first = await Promise.any(promises);
  console.log(最初);
} キャッチ (エラー) {
  コンソール.ログ(エラー);
}

上記のコードでは、Promise.any() メソッドのパラメータ配列に 3 つの Promise オペレーションが含まれています。そのうちの 1 つが「満たされる」限り、「Promise.any()」によって返される Promise オブジェクトは「満たされる」になります。 3 つの操作がすべて「拒否」された場合、「await」コマンドはエラーをスローします。

Promise.any() によってスローされるエラーは、AggregateError インスタンスです (詳細については、「オブジェクト拡張」の章を参照)。この AggregateError インスタンス オブジェクトの errors プロパティは、すべてのメンバーのエラーを含む配列です。

以下に例を示します。

varsolved = Promise.resolve(42);
var拒否 = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([解決済み、拒否されました、また拒否されました]).then(function (result) {
  コンソール.log(結果); // 42
});

Promise.any([拒否されました, また拒否されました]).catch(関数 (結果) {
  console.log(resultsinstanceofAggregateError); // true
  console.log(results.errors); // [-1, 無限]
});

Promise.resolve()

場合によっては、既存のオブジェクトを Promise オブジェクトに変換する必要があり、Promise.resolve() メソッドがこれを行います。

const jsPromise = Promise.resolve($.ajax('/whatever.json'));

上記のコードは、jQuery によって生成された deferred オブジェクトを新しい Promise オブジェクトに変換します。

Promise.resolve() は以下の記述に相当します。

Promise.resolve('foo')
// と同等
新しい約束(解決 => 解決('foo'))

Promise.resolve() メソッドのパラメータは 4 つの状況に分けられます。

(1) パラメータは Promise インスタンスです

パラメータが Promise インスタンスの場合、「Promise.resolve」は変更を加えずにインスタンスを返します。

(2) パラメータは「thenable」オブジェクトです

「thenable」オブジェクトは、次のオブジェクトなど、「then」メソッドを持つオブジェクトを指します。

let thenable = {
  次に: function(解決、拒否) {
    解決(42);
  }
};

Promise.resolve() メソッドは、このオブジェクトを Promise オブジェクトに変換し、すぐに thenable オブジェクトの then() メソッドを実行します。

let thenable = {
  次に: function(解決、拒否) {
    解決(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(関数(値) {
  コンソール.log(値); // 42
});

上記のコードでは、thenable オブジェクトの then() メソッドが実行された後、オブジェクト p1 のステータスが resolved に変わり、最後の then() メソッドで指定されたコールバック関数が呼び出されます。はすぐに実行され、42 が出力されます。

(3) パラメータは then() メソッドを持つオブジェクトではないか、またはまったくオブジェクトではありません

引数がプリミティブ値、または then() メソッドを持たないオブジェクトの場合、Promise.resolve() メソッドは解決済みのステータスを持つ新しい Promise オブジェクトを返します。

const p = Promise.resolve('Hello');

p.then(関数(s) {
  コンソールのログ
});
//こんにちは

上記のコードは、新しい Promise オブジェクト インスタンス p を生成します。文字列 Hello は非同期操作ではないため (文字列オブジェクトが then メソッドを持たないという判定方法)、返される Promise インスタンスの状態は生成された時点から 解決済み であるため、コールバック関数はすぐに実行されます。 Promise.resolve() メソッドのパラメータは同時にコールバック関数に渡されます。

(4) パラメータなし

「Promise.resolve()」メソッドはパラメータなしで呼び出すことができ、「解決された」状態の Promise オブジェクトを直接返します。

したがって、Promise オブジェクトを取得したい場合は、Promise.resolve() メソッドを直接呼び出す方が便利です。

const p = Promise.resolve();

p.then(関数() {
  // ...
});

上記のコードの変数 p は Promise オブジェクトです。

即時 resolve() の Promise オブジェクトは、次のラウンドの「イベント ループ」の開始時ではなく、このラウンドの「イベント ループ」(イベント ループ) の終了時に実行されることに注意してください。

setTimeout(関数() {
  console.log('3');
}, 0);

Promise.resolve().then(function () {
  console.log('2');
});

console.log('one');

// 1つ
// 二
// 三つ

上記のコードでは、setTimeout(fn, 0) は「イベント ループ」の次のラウンドの開始時に実行され、Promise.resolve() はこのラウンドの「イベント ループ」の終了時に実行されます。 console.log('one') `は即時に実行されるので最初に出力されます。

Promise.reject()

Promise.reject(reason) メソッドは、ステータスが rejected の新しい Promise インスタンスも返します。

const p = Promise.reject('エラーが発生しました');
// と同等
const p = new Promise((解決、拒否) =>拒否('エラーが発生しました'))

p.then(null, 関数 (s) {
  コンソールのログ
});
// 何か問題が発生しました

上記のコードは Promise オブジェクト「p」のインスタンスを生成し、ステータスは「拒否」され、コールバック関数がすぐに実行されます。

Promise.reject() メソッドのパラメータは、reject の理由として変更されず、後続のメソッドのパラメータになります。

Promise.reject('エラーが発生しました')
.catch(e => {
  console.log(e === 'エラーが発生しました')
})
// 真実

上記のコードでは、Promise.reject() メソッドのパラメータは文字列であり、後続の catch() メソッドのパラメータ e はこの文字列です。

応用

画像をロードする

画像の読み込みを「Promise」として記述することができます。読み込みが完了すると、「Promise」の状態が変わります。

const preloadImage = 関数 (パス) {
  return new Promise(function (解決、拒否) {
    const image = 新しい画像();
    image.onload = 解決;
    image.onerror = 拒否;
    画像.src = パス;
  });
};

Generator関数とPromiseの組み合わせ

プロセスを管理するには Generator 関数を使用します。非同期操作が発生すると、通常は Promise オブジェクトが返されます。

関数 getFoo () {
  return new Promise(function (解決、拒否){
    解決('foo');
  });
}

const g = 関数* () {
  試す {
    const foo = yield getFoo();
    コンソール.log(foo);
  } キャッチ (e) {
    コンソール.ログ(e);
  }
};

関数実行 (ジェネレーター) {
  const it = ジェネレーター();

  関数 go(結果) {
    if (result.done) return result.value;

    return result.value.then(関数 (値) {
      return go(it.next(value));
    }、関数(エラー){
      return go(it.throw(error));
    });
  }

  go(it.next());
}

実行(g);

上記のコードのジェネレーター関数 g には、Promise オブジェクトを返す非同期操作 getFoo があります。関数 run は、この Promise オブジェクトを処理し、次の next メソッドを呼び出すために使用されます。

Promise.try()

実際の開発では、関数 f が同期関数なのか非同期演算なのか分からない、あるいは区別したくないが、それを Promise で処理したい、という状況に遭遇することがよくあります。この方法では、f に非同期操作が含まれているかどうかに関係なく、then メソッドを使用してプロセスの次のステップを指定でき、catch メソッドを使用して f' によってスローされたエラーを処理できます。 。一般的には以下のような書き込み方法が用いられます。

Promise.resolve().then(f)

上記の書き方の欠点の 1 つは、f が同期関数の場合、イベント ループのこのラウンドの最後に実行されることです。

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('次');
// 次
// 今

上記のコードでは、関数 f は同期実行ですが、Promise でラップすると非同期実行になります。

それでは、同期関数を同期的に実行し、非同期関数を非同期的に実行し、それらに統一された API を持たせる方法はあるのでしょうか?答えは「はい」です。書き方は 2 つあります。これを記述する最初の方法は、async 関数を使用することです。

const f = () => console.log('now');
(async () => f())();
console.log('次');
// 今
// 次

上記のコードでは、2 行目は即時実行される匿名関数であり、その中の async 関数が即時に実行されます。したがって、f が同期の場合、f が同期の場合は同期の結果が得られます。非同期、then 以下に示すように、「then」を使用して次のステップを指定できます。

(async () => f())()
。それから(...)

async () => f() は、f() によってスローされたエラーを処理することに注意してください。したがって、エラーをキャッチしたい場合は、promise.catch メソッドを使用してください。

(async () => f())()
。それから(...)
。キャッチ(...)

2 番目の記述方法は、new Promise() を使用することです。

const f = () => console.log('now');
(
  () => 新しい約束(
    解決 => 解決(f())
  )
)();
console.log('次');
// 今
// 次

上記のコードでは、new Promise() を実行するためにすぐに実行される匿名関数も使用しています。この場合、同期関数も同期的に実行されます。

これは非常に一般的な要件であるため、上記の記述方法を置き換える Promise.try メソッドを提供する プロポーザル が用意されています。

const f = () => console.log('now');
Promise.try(f);
console.log('次');
// 今
// 次

実際、Promise.try は長い間存在しており、Promise ライブラリ Bluebird、[Q]( https://github .com/kriskowal/q/wiki/API-Reference#promisefcallargs) および [when](https://github.com/cujojs/when/blob/master/docs/api.md#whentry )、長い間提供されてきたこの方法を入手しました。

Promise.try はすべての操作に対して統一された処理メカニズムを提供するため、then メソッドを使用してプロセスを管理したい場合は、Promise.try でラップするのが最善です。これには 多くの利点 があり、そのうちの 1 つは次のとおりです。例外の管理が改善されること。

関数 getUsername(userId) {
  returndatabase.users.get({id:userId})
  .then(関数(ユーザー) {
    ユーザー名を返します;
  });
}

上記のコードでは、database.users.get() は Promise オブジェクトを返します。非同期エラーがスローされた場合は、以下に示すように、catch メソッドを使用してキャプチャできます。

Database.users.get({id: userId})
。それから(...)
。キャッチ(...)

ただし、database.users.get() は同期エラー (実装方法によってはデータベース接続エラーなど) をスローする場合もあります。その場合は、try...catch を使用して同期エラーをキャッチする必要があります。

試す {
  Database.users.get({id: userId})
  。それから(...)
  。キャッチ(...)
} キャッチ (e) {
  // ...
}

上記の記述方法は非常に不格好です。この場合、promise.catch() を使用してすべての同期エラーと非同期エラーをキャッチできます。

Promise.try(() =>database.users.get({id: userId}))
  。それから(...)
  。キャッチ(...)

実際、「promise.catch」が「catch」コード ブロックをシミュレートするのと同じように、「Promise.try」は「try」コード ブロックをシミュレートします。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0