2015年以降にフロントエンドエンジニアになったなら、おそらく「Promise」を使ったことがあるか、少なくとも聞いたことがあるでしょう。 前回のブログで触れたように、「Promise」は非同期プログラミングにおける操作間の逐次関係を読みやすく保守しやすく指定するのに役立ちます。JavaScriptの「Promise」オブジェクトには「then」や「catch」などのメソッドがあり、Promise Chainingというパターンで操作の連続実行を整理するのに役立ちます。Promise Chainingはクリーンで整理されていますが、特に同期プログラミングに慣れている人には直感的で混乱を感じる人もいます。幸いなことに、ECMAScript 2017では非同期コードを同期的に書くことを可能にする新機能が導入されました。この新機能は「async」と「await」という2つのキーワードで構成されています。この記事では、「async/waitit」の基本と、Promise Chainingから「async/waitit」への移行方法を解説します。
待つ:約束が果たされるまでの待ち
まずはキーワード「await」から始めます。「await」は「Promise」オブジェクトを返す関数を呼び出す際に使います。この「Promise」オブジェクトの完了(または失敗)まで、その後のすべての操作をブロックします。また、「Promise」オブジェクトの履行値を定数や変数に割り当てるのにも役立ちます。以下はその主な用途を示す例です。
function getNumberOneInOneOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
解決(1)
}, 1000)
})
}
例えば、「getNumberOneInOneOneOneSecond」という関数が「Promise」オブジェクトを返すとします。この「Promise」オブジェクトを構築する際、コンストラクタにリゾルブ関数の引数を持つ匿名関数を渡します。匿名関数では、1秒後に「resolve」関数を呼び出して「setTimeOut」を使い、引数が1の関数を呼び出します。
もし「getNumberOneInOneOneSecond」の返された値で定数を初期化すると、その定数は「Promise」のインスタンスとなります。
function getNumberOneInOneOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
解決(1)
}, 1000)
})
}
const numberOnePromise = getNumberOneInOneOneOneSecond()
console.log(numberOnePromise Promiseのインスタンス)
しかし直感的には、定数がナンバーワンであることが期待されます。ここで「await」キーワードが役立ちます。「wait」キーワードを使って、「Promise」オブジェクトの履行値を定数「numberOne」に直接割り当てることができます。前述の通り、「wait」は「Promise」オブジェクトがまだ保留中の場合、その後のすべての操作をブロックします。したがって、コンソールは「Promise」オブジェクトが満たされた後にのみ定数「numberOne」を出力します。
function getNumberOneInOneOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
解決(1)
}, 1000);
})
}
非同期関数 main() {
console.log(『1番を手に入れる...』) // 即刻実行
const numberOne = waititgetNumberOneInOneOneSecond()
console.log(ナンバーワン)
}
main()
アシンク:待つための聖域と約束を生み出す工場
「await」キーワードは非同期プログラミングで同期的なコードを書くのに役立ちますが、好きな場所で使うことはできません。なぜ「waitit getOneInOneOneOneSecond()」を「main」関数にラップし、上記のコード例で「async」とマークしたのか、混乱しているかもしれません。もちろん、単なる遊びでやったわけではありません。「await」キーワードは「async」とマークされた関数でしか使えないからです。
さらに、「async」は通常の関数を「Promise」オブジェクトを返す関数に変換できます。例えば「multiplyByTwo」という関数があるとします。これは1つの引数を取ります。まず、引数が数字かどうかを確認します。引数が数字であれば、「multiplyByTwo」はそれを2倍して積を返します。そうでなければ、「multiplyByTwo」はエラーを投げ出します。
function multiplyByTwo(input) {
もし(typeof input === "number") {
コンセント出力 = 入力 * 2
出力を返す
} そうでなければ {
const error = 新しいエラー("Invalid Input")
投げエラー
}
}
関数の前に「async」キーワードを加えると、その関数は「Promise」オブジェクトを返します。返された「Promise」オブジェクトは元の関数の返却値で解決され、元の関数が投げたエラーで却下されます。
async 関数 multiplyByTwo(input) {
もし(typeof input === "number") {
コンセント出力 = 入力 * 2
出力を返す
} そうでなければ {
const error = 新しいエラー("Invalid Input")
投げエラー
}
}
これは 前回のブログで例として使った次の関数と同等です。
function multiplyByTwo(input) {
return new Promise((resolve, reject) => {
もし(typeof input === "number") {
コンセント出力 = 入力 * 2
Resolve(出力)
} そうでなければ {
const error = 新しいエラー("Invalid Input")
reject(エラー)
}
})
}
ご覧の通り、「async」キーワードは非同期プログラミングで同期的な関数を書くことを可能にします。関数内でPromiseを返すたびに「Promise」オブジェクトを構築する必要がなくなるだけでなく、「resolve」や「reject」といった慣れない関数をコードベースに含めるのを避ける助けにもなります。
Try/Catch:エラーも同期型で処理できます
ここで「キャッチ法を使わずにエラーをどう扱うのか?」と疑問に思うかもしれません。答えは驚くほどシンプルです!私たちは昔ながらの友人である「try/catch」の構造を使えます。
async 関数 multiplyByTwo(input) {
もし(typeof input === "number") {
コンセント出力 = 入力 * 2
出力を返す
} そうでなければ {
const error = 新しいエラー("Invalid Input")
投げエラー
}
}
非同期関数 main() {
試してみて {
const output = waitimultiplyByTwo("2")
console.log(出力)
} キャッチ(e) {
console.log(e)
}
}
main()
「multiplyByTwo」が投げる可能性のあるエラーを処理するために、その呼び出しを「try」ブロックでラップし、エラー処理操作を「catch」ブロックで定義します(私たちの場合はエラーを表示するだけです)。
アシンク/アウェイト:約束の鎖から解放
次に、async/awaitを使って、Promise Chainingを利用した以下のコード例を書き直す方法を実演します。
function addExclamationMark(input) {
return new Promise((resolve, reject) => {
もし(typeof input === "string") {
const output = input + "!"
Resolve(出力)
} そうでなければ {
const error = 新しいエラー("Invalid Input")
reject(エラー)
}
})
}
addExclamationMark(「こんにちは、世界」)
.then(addExclamationMark) // .then((input) => addExclamationMark(input))と同じです
.then(addExclamationMark)
.then(addExclamationMark)
.then(addExclamationMark)
.then((output) => console.log(output))
.catch((error) => console.log(error))
まず「addExclamationMark」の定義の前に「async」キーワードを追加します。次に、コア操作を展開するための新しい「Promise」オブジェクトの構成を削除します。その後、「resolve」と「reject」の呼び出しをそれぞれ「return」と「throw」文に置き換えます。
async 関数 addExclamationMark(input) {
もし(typeof input === "string") {
const output = input + "!"
出力を返す
} そうでなければ {
const error = 新しいエラー("Invalid Input")
投げエラー
}
}
非同期関数 main() {
試してみて {
const output1 = await addExclamationMark("こんにちは、世界")
const output2 = waitit addExclamationMark(output1)
const output3 = await addExclamationMark(output2)
const output4 = waitit addExclamationMark(output3)
const output5 = await addExclamationMark(output4)
console.log(output5)
} キャッチ(エラー) {
console.log(エラー)
}
}
main()
約束の連鎖を置き換えるために、「addExclamationMark」の5つの呼び出しを完全に分離します。「addExclamationMark」の呼び出しの前に「wait」を置き、出力を新しい定数に割り当てます。また、前の呼び出しの「addExclamationMark」の出力を次の呼び出しに引数として渡します。最後に、最終出力を「console.log(output5)」を呼び出して表示します。
以前「catch」メソッドでラップされていたエラー処理操作は、今度は「catch」ブロックに置きます。「catch」ブロックの進行は「try」ブロックで、何も問題が起きなかった場合に実行したいすべての操作を含む「addExclamationMark」の5回の呼び出しが含まれます。
両方の良いところを手に入れられる
「async/wait」を完全に使いたいとは思わない方へ、良いニュースがあります。両方の良いところを手に入れることができます!例えば、非同期関数(「Promise」オブジェクトを返す関数)を定義する上で「async」を使う方が良いと納得しているとしましょう。しかし、Promise Chainingを使う自由を放棄したくはありません。「await」キーワードでコードベースを埋め尽くす代わりに、Promise Chainingで「async」を使うことができます。望むスタイルであれば、「await」とPromise Chainを混同することも可能です。世界はあなたのものです!
この記事では、非同期プログラミングにおける連続実行の通常の方法(約束チェーン)に対する強力な代替手段である「async/await」の基本を紹介しました。まず、「async/waitit」の使い方とその条件を説明しました。次に、「async/waitit」で非同期コードを書き換える方法を実演しました。最後に、「async」と「wait」を別々かつ選択的に使うことは絶対的なコミットメントなしに全く問題ないと述べました。この記事が「async」と「wait」の使用を始める、あるいは検討するきっかけになれば幸いです。