JavaScriptのジェネレーターを理解する
序章
ECMAScript 2015 では、ジェネレーターがJavaScript言語に導入されました。 ジェネレーターは、一時停止および再開できるプロセスであり、複数の値を生成できます。 JavaScriptのジェネレーターは、ジェネレーター関数で構成され、反復可能なGeneratorオブジェクトを返します。
ジェネレーターは状態を維持し、イテレーターを効率的に作成する方法を提供し、Webアプリケーションのフロントエンドに無限スクロールを実装したり、音波データを操作したりするために使用できる無限データストリームを処理できます。 さらに、 Promises と併用すると、ジェネレーターは async/await
非同期コードをより簡単で読みやすい方法で処理できるようにする機能。 それでも async/await
APIからデータをフェッチするなど、一般的で単純な非同期のユースケースを処理するためのより一般的な方法です。ジェネレーターには、それらの使用方法を学習する価値のある、より高度な機能があります。
この記事では、ジェネレーター関数を作成する方法、反復する方法について説明します。 Generator
オブジェクト、間の違い yield
と return
ジェネレーターの内部、およびジェネレーターの操作の他の側面。
ジェネレーター機能
ジェネレーター関数は、 Generator
オブジェクトであり、によって定義されます function
キーワードの後にアスタリスク(*
)、次のようになります。
// Generator function declaration
function* generatorFunction() {}
場合によっては、関数のキーワードではなく、関数名の横にアスタリスクが表示されることがあります。 function *generatorFunction()
. これは同じように機能しますが function*
より広く受け入れられている構文です。
ジェネレーター関数は、通常の関数のように、式で定義することもできます。
// Generator function expression
const generatorFunction = function*() {}
ジェネレーターは、オブジェクトまたはクラスのメソッドにすることもできます。
// Generator as the method of an object
const generatorObj = {
*generatorMethod() {},
}
// Generator as the method of a class
class GeneratorClass {
*generatorMethod() {}
}
この記事全体の例では、ジェネレーター関数の宣言構文を使用します。
注:通常の関数とは異なり、ジェネレーターは new
キーワードであり、矢印関数と組み合わせて使用することもできません。
ジェネレータ関数を宣言する方法がわかったので、イテレータを見てみましょう。 Generator
それらが返すオブジェクト。
ジェネレータオブジェクト
従来、JavaScriptの関数は最後まで実行され、関数を呼び出すと、関数に到達したときに値が返されます。 return
キーワード。 の場合 return
キーワードを省略すると、関数は暗黙的に戻ります undefined
.
たとえば、次のコードでは、 sum()
2つの整数引数の合計である値を返す関数:
// A regular function that sums two values
function sum(a, b) {
return a + b
}
関数を呼び出すと、引数の合計である値が返されます。
sum(5, 6) // 11
ただし、ジェネレータ関数はすぐに値を返すのではなく、イテレータを返します Generator
物体。 次の例では、関数を宣言し、標準関数のように単一の戻り値を与えます。
// Declare a generator function with a single return value
function* generatorFunction() {
return 'Hello, Generator!'
}
ジェネレーター関数を呼び出すと、 Generator オブジェクトが返されます。これは、変数に割り当てることができます。
// Assign the Generator object to generator
const generator = generatorFunction();
これが通常の機能である場合、 generator
関数で返される文字列を取得します。 ただし、実際に取得するのは、 suspended
州。 呼び出し generator
したがって、次のような出力が得られます。
OutputgeneratorFunction {<suspended>}
__proto__: Generator
[[GeneratorLocation]]: VM272:1
[[GeneratorStatus]]: "suspended"
[[GeneratorFunction]]: ƒ* generatorFunction()
[[GeneratorReceiver]]: Window
[[Scopes]]: Scopes[3]
The Generator
関数によって返されるオブジェクトはイテレータです。 イテレータは、 next()
利用可能なメソッド。値のシーケンスを反復処理するために使用されます。 The next()
メソッドは次のオブジェクトを返します value
と done
プロパティ。 value
戻り値を表し、 done
イテレータがすべての値を実行したかどうかを示します。
これを知って、電話しましょう next()
私たちに generator
イテレータの現在の値と状態を取得します。
// Call the next method on the Generator object
generator.next()
これにより、次の出力が得られます。
Output{value: "Hello, Generator!", done: true}
呼び出しから返された値 next()
は Hello, Generator!
、およびの状態 done
は true
、この値は return
それはイテレータを閉じました。 イテレータが実行されたため、ジェネレータ関数のステータスは次のように変更されます。 suspended
に closed
. 呼び出し generator
再び次のようになります。
OutputgeneratorFunction {<closed>}
今のところ、ジェネレーター関数がどのようにしてより複雑な方法で return
関数の値。 ただし、ジェネレーター関数には、通常の関数とは異なる独自の機能もあります。 次のセクションでは、 yield
演算子を使用して、ジェネレータが実行を一時停止および再開する方法を確認してください。
yield
演算子
ジェネレーターはJavaScriptに新しいキーワードyieldを導入します。 yield
ジェネレーター関数を一時停止して、次の値を返すことができます yield
、値を反復処理するための軽量な方法を提供します。
この例では、ジェネレーター関数を異なる値で3回一時停止し、最後に値を返します。 次に、 Generator
に反対する generator
変数。
// Create a generator function with multiple yields
function* generatorFunction() {
yield 'Neo'
yield 'Morpheus'
yield 'Trinity'
return 'The Oracle'
}
const generator = generatorFunction()
今、私たちが呼び出すとき next()
ジェネレーター機能では、遭遇するたびに一時停止します yield
. done
に設定されます false
それぞれの後に yield
、ジェネレータが終了していないことを示します。 遭遇したら return
、またはこれ以上ありません yield
関数で検出された、 done
に反転します true
、ジェネレーターが終了します。
使用 next()
4回続けてメソッド:
// Call next four times
generator.next()
generator.next()
generator.next()
generator.next()
これらにより、次の4行の出力が順番に表示されます。
Output{value: "Neo", done: false}
{value: "Morpheus", done: false}
{value: "Trinity", done: false}
{value: "The Oracle", done: true}
ジェネレータは必要ないことに注意してください return
; 省略した場合、最後の反復は戻ります {value: undefined, done: true}
、以降の呼び出しも同様です next()
ジェネレータが完了した後。
ジェネレーターでの反復
を使用して next()
メソッドでは、手動で繰り返しました Generator
オブジェクト、すべてを受け取る value
と done
完全なオブジェクトのプロパティ。 ただし、 Array 、 Map、およびSet と同様に、 Generator
反復プロトコルに従い、 for …ofで反復できます。
// Iterate over Generator object
for (const value of generator) {
console.log(value)
}
これにより、次が返されます。
OutputNeo
Morpheus
Trinity
スプレッド演算子を使用して、の値を割り当てることもできます。 Generator
配列に。
// Create an array from the values of a Generator object
const values = [...generator]
console.log(values)
これにより、次の配列が得られます。
Output(3) ["Neo", "Morpheus", "Trinity"]
スプレッドと for...of
因数分解されません return
値に(この場合、 'The Oracle'
).
注:これらの方法はどちらも有限のジェネレーターでの作業に効果的ですが、ジェネレーターが無限のデータストリームを処理している場合、spreadまたは for...of
無限ループを作成せずに直接。
ジェネレーターを閉じる
これまで見てきたように、ジェネレーターは done
プロパティをに設定 true
とそのステータスはに設定されます closed
そのすべての値を反復することによって。 ジェネレータをすぐにキャンセルするには、さらに2つの方法があります。 return()
メソッド、および throw()
方法。
と return()
、ジェネレータは、あたかも return
ステートメントは関数本体にありました。 に引数を渡すことができます return()
、または未定義の値の場合は空白のままにします。
デモンストレーションする return()
、いくつかのジェネレータを作成します yield
値はありません return
関数定義:
function* generatorFunction() {
yield 'Neo'
yield 'Morpheus'
yield 'Trinity'
}
const generator = generatorFunction()
最初 next()
私たちに 'Neo'
、 と done
に設定 false
. を呼び出すと return()
上のメソッド Generator
その直後のオブジェクト、渡された値を取得し、 done
に設定 true
. 追加の呼び出し next()
未定義の値でデフォルトの完了したジェネレーター応答を返します。
これを実証するために、次の3つのメソッドを実行します。 generator
:
generator.next()
generator.return('There is no spoon!')
generator.next()
これにより、次の3つの結果が得られます。
Output{value: "Neo", done: false}
{value: "There is no spoon!", done: true}
{value: undefined, done: true}
The return()
メソッドは強制的に Generator
完了し、他のオブジェクトを無視するオブジェクト yield
キーワード。 これは、Promiseを直接キャンセルすることはできないため、ユーザーが別のアクションを実行したいときにWeb要求を中断するなど、関数をキャンセル可能にする必要がある非同期プログラミングで特に役立ちます。
ジェネレーター関数の本体にエラーをキャッチして処理する方法がある場合は、 throw()
ジェネレータにエラーをスローするメソッド。 これにより、ジェネレーターが起動し、エラーがスローされ、ジェネレーターが終了します。
これを実証するために、ジェネレーター関数本体内に try … catch を配置し、エラーが見つかった場合はログに記録します。
// Define a generator function with a try...catch
function* generatorFunction() {
try {
yield 'Neo'
yield 'Morpheus'
} catch (error) {
console.log(error)
}
}
// Invoke the generator and throw an error
const generator = generatorFunction()
今、私たちは実行します next()
メソッド、続いて throw()
:
generator.next()
generator.throw(new Error('Agent Smith!'))
これにより、次の出力が得られます。
Output{value: "Neo", done: false}
Error: Agent Smith!
{value: undefined, done: true}
使用する throw()
、ジェネレータにエラーを挿入しましたが、 try...catch
コンソールにログインしました。
ジェネレータオブジェクトのメソッドと状態
次の表に、で使用できるメソッドのリストを示します。 Generator
オブジェクト:
次の表に、 Generator
物体:
状態 | 説明 |
---|---|
suspended |
ジェネレータは実行を停止しましたが、終了していません |
closed |
ジェネレータは、エラーが発生するか、戻るか、すべての値を反復処理することによって終了しました |
yield
委任
通常に加えて yield
演算子の場合、ジェネレーターは yield * 式を使用して、さらに値を別のジェネレーターに委任することもできます。 いつ yield*
ジェネレーター内で検出されると、委任されたジェネレーター内に入り、すべての yield
sそのジェネレータが閉じられるまで。 これを使用して、さまざまなジェネレーター関数を分離し、コードを意味的に整理しながら、すべてのコードを保持することができます。 yield
正しい順序で反復可能である。
実例を示すために、2つのジェネレーター関数を作成できます。そのうちの1つは yield*
もう一方を操作します。
// Generator function that will be delegated to
function* delegate() {
yield 3
yield 4
}
// Outer generator function
function* begin() {
yield 1
yield 2
yield* delegate()
}
次に、繰り返してみましょう begin()
母関数:
// Iterate through the outer generator
const generator = begin()
for (const value of generator) {
console.log(value)
}
これにより、生成された順序で次の値が得られます。
Output1
2
3
4
外側のジェネレーターは値を生成しました 1
と 2
、次に他のジェネレータに委任されます yield*
、戻ってきた 3
と 4
.
yield*
配列やマップなど、反復可能な任意のオブジェクトに委任することもできます。 ジェネレーター内で使用したい関数があるため、歩留まりの委任はコードの整理に役立ちます。 yield
また、ジェネレーターである必要があります。
無限のデータストリーム
ジェネレーターの便利な側面の1つは、無限のデータストリームとコレクションを処理する機能です。 これは、数値を1つインクリメントするジェネレーター関数内に無限ループを作成することで実証できます。
次のコードブロックでは、このジェネレーター関数を定義してから、ジェネレーターを開始します。
// Define a generator function that increments by one
function* incrementer() {
let i = 0
while (true) {
yield i++
}
}
// Initiate the generator
const counter = incrementer()
ここで、を使用して値を繰り返し処理します next()
:
// Iterate through the values
counter.next()
counter.next()
counter.next()
counter.next()
これにより、次の出力が得られます。
Output{value: 0, done: false}
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
この関数は、無限ループで連続する値を返します。 done
プロパティは残ります false
、終了しないようにします。
ジェネレーターを使用すると、実行を自由に停止および再開できるため、無限ループの作成について心配する必要はありません。 ただし、ジェネレータを呼び出す方法には注意が必要です。 スプレッドまたはを使用する場合 for...of
無限データストリームでは、無限ループを一度に繰り返し処理するため、環境がクラッシュします。
無限データストリームのより複雑な例として、フィボナッチジェネレーター関数を作成できます。 前の2つの値を連続的に加算するフィボナッチ数列は、ジェネレーター内の無限ループを使用して次のように記述できます。
// Create a fibonacci generator function
function* fibonacci() {
let prev = 0
let next = 1
yield prev
yield next
// Add previous and next values and yield them forever
while (true) {
const newVal = next + prev
yield newVal
prev = next
next = newVal
}
}
これをテストするために、有限数をループして、フィボナッチ数列をコンソールに出力できます。
// Print the first 10 values of fibonacci
const fib = fibonacci()
for (let i = 0; i < 10; i++) {
console.log(fib.next().value)
}
これにより、次のようになります。
Output0
1
1
2
3
5
8
13
21
34
無限のデータセットを処理する機能は、ジェネレーターを非常に強力にするものの一部です。 これは、Webアプリケーションのフロントエンドに無限スクロールを実装するような例で役立ちます。
ジェネレーターで値を渡す
この記事全体を通して、イテレーターとしてジェネレーターを使用し、各反復で値を生成しました。 ジェネレータは、値を生成するだけでなく、からの値を消費することもできます。 next()
. この場合、 yield
値が含まれます。
最初のことに注意することが重要です next()
呼び出された場合、値は渡されませんが、ジェネレーターが起動するだけです。 これを実証するために、次の値をログに記録できます。 yield
と電話 next()
いくつかの値で数回。
function* generatorFunction() {
console.log(yield)
console.log(yield)
return 'The end'
}
const generator = generatorFunction()
generator.next()
generator.next(100)
generator.next(200)
これにより、次の出力が得られます。
Output100
200
{value: "The end", done: true}
ジェネレータに初期値をシードすることもできます。 次の例では、 for
ループして各値をに渡します next()
メソッドですが、初期関数にも引数を渡します。
function* generatorFunction(value) {
while (true) {
value = yield value * 10
}
}
// Initiate a generator and seed it with an initial value
const generator = generatorFunction(0)
for (let i = 0; i < 5; i++) {
console.log(generator.next(i).value)
}
から値を取得します next()
そして、次の反復に新しい値を生成します。これは、前の値に10を掛けたものです。 これにより、次のようになります。
Output0
10
20
30
40
ジェネレーターの起動に対処する別の方法は、ジェネレーターを常に呼び出す関数でラップすることです。 next()
他のことをする前に一度。
async
/await
ジェネレーター付き
非同期関数は、ES6 + JavaScriptで使用できる関数の一種であり、非同期データを同期的に表示することで、非同期データの操作を理解しやすくします。 ジェネレーターには、非同期関数よりも広範な機能がありますが、同様の動作を複製することができます。 この方法で非同期プログラミングを実装すると、コードの柔軟性を高めることができます。
このセクションでは、ジェネレーターを使用して async /awaitを再現する例を示します。
FetchAPIを使用してJSONPlaceholderAPI (テスト目的で JSON データの例を提供)からデータを取得し、応答をログに記録する非同期関数を作成してみましょう。コンソール。
と呼ばれる非同期関数を定義することから始めます getUsers
APIからデータをフェッチし、オブジェクトの配列を返し、呼び出します getUsers
:
const getUsers = async function() {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const json = await response.json()
return json
}
// Call the getUsers function and log the response
getUsers().then(response => console.log(response))
これにより、次のようなJSONデータが得られます。
Output[ {id: 1, name: "Leanne Graham" ...},
{id: 2, name: "Ervin Howell" ...},
{id: 3, name": "Clementine Bauch" ...},
{id: 4, name: "Patricia Lebsack"...},
{id: 5, name: "Chelsey Dietrich"...},
...]
ジェネレーターを使用すると、を使用しないほぼ同一のものを作成できます。 async
/await
キーワード。 代わりに、私たちが作成した新しい関数を使用し、 yield
の代わりに値 await
約束します。
次のコードブロックでは、次の関数を定義します。 getUsers
それは私たちの新しいを使用します asyncAlt
模倣する関数(後で記述します) async
/await
.
const getUsers = asyncAlt(function*() {
const response = yield fetch('https://jsonplaceholder.typicode.com/users')
const json = yield response.json()
return json
})
// Invoking the function
getUsers().then(response => console.log(response))
ご覧のとおり、ほぼ同じように見えます async
/await
値を生成するジェネレーター関数が渡されることを除いて、実装。
これで、 asyncAlt
非同期関数に似た関数。 asyncAlt
パラメータとしてジェネレータ関数があります。これは、次のような約束を生成する関数です。 fetch
戻り値。 asyncAlt
関数自体を返し、最後のpromiseまで見つかったすべてのpromiseを解決します。
// Define a function named asyncAlt that takes a generator function as an argument
function asyncAlt(generatorFunction) {
// Return a function
return function() {
// Create and assign the generator object
const generator = generatorFunction()
// Define a function that accepts the next iteration of the generator
function resolve(next) {
// If the generator is closed and there are no more values to yield,
// resolve the last value
if (next.done) {
return Promise.resolve(next.value)
}
// If there are still values to yield, they are promises and
// must be resolved.
return Promise.resolve(next.value).then(response => {
return resolve(generator.next(response))
})
}
// Begin resolving promises
return resolve(generator.next())
}
}
これにより、 async
/await
バージョン:
Output[ {id: 1, name: "Leanne Graham" ...},
{id: 2, name: "Ervin Howell" ...},
{id: 3, name": "Clementine Bauch" ...},
{id: 4, name: "Patricia Lebsack"...},
{id: 5, name: "Chelsey Dietrich"...},
...]
この実装は、の代わりにジェネレータを使用する方法を示すためのものであることに注意してください。 async
/await
、および本番環境に対応した設計ではありません。 エラー処理は設定されておらず、生成された値にパラメーターを渡す機能もありません。 この方法はコードに柔軟性を加えることができますが、多くの場合 async/await
実装の詳細を抽象化し、生産的なコードの記述に集中できるため、より適切な選択になります。
結論
ジェネレーターは、実行を停止および再開できるプロセスです。 これらは、一般的には使用されていませんが、JavaScriptの強力で用途の広い機能です。 このチュートリアルでは、ジェネレーター関数とジェネレーターオブジェクト、ジェネレーターで使用可能なメソッド、 yield
と yield*
演算子、および有限および無限のデータセットで使用されるジェネレータ。 また、ネストされたコールバックや長いPromiseチェーンを使用せずに非同期コードを実装する1つの方法についても検討しました。
JavaScript構文の詳細については、 JavaScript でのこれ、バインド、呼び出し、および適用の理解とJavaScriptでのオブジェクトのマップと設定の理解のチュートリアルを参照してください。