非同期反復とは何ですか? 反復をカスタマイズするにはどうすればよいですか? ES6 イテレータとジェネレータの詳細な説明

非同期反復とは何ですか?  反復をカスタマイズするにはどうすればよいですか?  ES6 イテレータとジェネレータの詳細な説明

イテレータ

イテレーターは、データを消費して一度に 1 ステップずつ動作を制御するための、順序付けされたシーケンシャルなプルベースの組織です。

簡単に言うと、反復可能なオブジェクトを繰り返してループし、すべてのデータを一度に返すのではなく、関連するメソッドを呼び出してバッチで返します。

イテレータは、特定のデータ構造をトラバースするのに役立つオブジェクトです。これには、反復シーケンス内の現在の関数によって定義された値を指す属性を持つ値を返す関数がobject含まれています。nextvaluedoneobjectvaluenext

{
    
    
  done: boolean, // 为 true 时代表迭代完毕
  value: any     // done 为 true 时取值为 undefined
}

反復プロトコル

ES6 のイテレーション プロトコルはイテレータ プロトコル (イテレータ プロトコル) とイテラブル プロトコル (イテラブル プロトコル)に分かれており、イテレータはこの 2 つのプロトコルに基づいて実装されています。

Iterator Protocol: シーケンスを生成する標準的な方法iterator协议を定義します。valueこのオブジェクトは、必要な関数を実装している限りnextイテレータです。データベースのカーソルに似た、データ構造の要素を横断するための非常にポインタです。

反復可能プロトコル:反復可能プロトコルがサポートされると、オブジェクトをトラバーサルfor-ofに使用でき。Array&などの一般的な組み込み型は、Map反復可能なプロトコルをサポートしています。オブジェクトは@@iteratorメソッドを実装する必要があります。つまり、オブジェクトにはアクセス@@iterator key可能な定数を持つプロパティが必要ですSymbol.iterator

イテレータの実装をシミュレートする

イテレータプロトコルに基づく

// 实现
function createArrayIterator(arr) {
    
    
  let index = 0
  return {
    
    
    next: () =>
    index < arr.length
    ? {
    
     value: arr[index++], done: false }
    : {
    
     value: undefined, done: true },
  }
}

// 测试
const nums = [11, 22, 33, 44]
const numsIterator = createArrayIterator(nums)
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())

反復可能なプロトコルに基づく

イテレータを生成するメソッドを実装するオブジェクトが呼び出されます可迭代对象。つまり、このオブジェクトにはイテレータ オブジェクトを返すメソッドが含まれています。

一般にSymbol.iteratorこの属性を定義するために使用され、学名は@@iteratorメソッドと呼ばれます。

// 一个可迭代对象需要具有[Symbol.iterator]方法,并且这个方法返回一个迭代器
const obj = {
    
    
  names: ['111', '222', '333'],
  [Symbol.iterator]() {
    
    
    let index = 0
    return {
    
    
      next: () =>
      index < this.names.length
      ? {
    
     value: this.names[index++], done: false }
      : {
    
     value: undefined, done: true },
      return: () => {
    
    
        console.log('迭代器提前终止了...')
        return {
    
     value: undefined, done: true }
      },
    }
  },
}

// 测试
for (const item of obj) {
    
    
  console.log(item)
  if (item === '222') break
}

上記の 2 つの模擬イテレーターの例では、まだ比較的複雑ですが、ES6 ではジェネレーターオブジェクトが導入されており、イテレーター オブジェクトの作成プロセスがはるかに簡単になります。

ビルダー

ジェネレーターは、キーワードの後のアスタリスク (*****) で示される反復子を返す関数であり、新しいキーワードは関数で使用されますfunctionyield

// 生成器
function* creatIterator (){
    
    
    yield 1
    yield 2
    yield 3
}
const iterator = creatIterator()
console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}

上記の例では、creatIterator()先頭のアスタリスク * はジェネレータであることを示しており、yieldイテレータ メソッドを呼び出すnext()ときの戻り値と戻り順序はキーワードによって指定されます。

yield ステートメントが実行されると、関数は自動的に実行を停止します上記の例を例に挙げると、ステートメントの実行後、関数は他の言語を実行せず、yield 1反復子メソッドが再度呼び出されるまでステートメントnext()は続行されませんyield 2

注:yield式はジェネレーター関数でのみ使用でき、他の場所で使用するとエラーが報告されます。

(function (){
    
    
  yield 1;
})()
// SyntaxError: Unexpected number

function注: ES6 では、キーワードと関数名の間のアスタリスクをどこに記述するかは規定されていません。これにより、次のような書き込みが可能になります。

function * foo(x, y) {
    
     ··· }
function *foo(x, y) {
    
     ··· }
function* foo(x, y) {
    
     ··· }
function*foo(x, y) {
    
     ··· }

ジェネレータパスパラメータ

yield式自体は値を返さないか、常に を返しますundefinedこのメソッドは、前の式の戻り値nextとして使用されるパラメーターを 1 つ受け取ることができます。yield

function* dr(arg) {
    
    
  console.log(arg)
  let one = yield '111'
  console.log(one)
  yield '222'
  console.log('ccc')
}
let iterator = dr('aaa')
console.log(iterator.next())
console.log(iterator.next('bbb'))
console.log(iterator.next())

ここに画像の説明を挿入

アプリケーションシナリオ

日常の開発において、次のインターフェイスが前のインターフェイスのデータに依存する場合、ジェネレーターは非同期コールバック地獄のネストの問題を考慮せずに使用できます。

シミュレーション: 1秒後にユーザーデータ、2秒後に注文情報、3秒後に製品情報を取得

function getUser() {
    
    
  setTimeout(() => {
    
    
    const data = '用户数据'
    iterator.next(data)
  }, 1000)
}

function getOrder() {
    
    
  setTimeout(() => {
    
    
    const data = '订单信息'
    iterator.next(data)
  }, 2000)
}

function getGoods() {
    
    
  setTimeout(() => {
    
    
    const data = '商品数据'
    iterator.next(data)
  }, 3000)
}


function* initData() {
    
    
  const user = yield getUser()
  console.log(user)
  const order = yield getOrder()
  console.log(order)
  const goods = yield getGoods()
  console.log(goods)
}
const iterator = initData()
iterator.next()

ここに画像の説明を挿入

for ofこのループはイテレータと密接に関連しているため、ループはキー値のペアのキー値を取得できます。そのため、ここでそれについて説明します。

Symbol.iteratorデータ構造は R インターフェイスを持つとみなされ、プロパティがデプロイされている限りiterato使用できfor of、反復可能なオブジェクトをループできます。

JavaScriptデフォルトのiterableインターフェースを備えたデータ構造:

  • 配列 配列
  • 地図
  • 設定
  • 引数オブジェクト
  • ノードリスト オブジェクト、クラス配列、およびiteratorインターフェイスを展開するデータ構造では、配列の拡散演算子 (…) や代入の構造化などの操作を使用できます。

配列を反復処理する

for または を使用して配列をループしてみてください

ここに画像の説明を挿入

配列はfor...ofループをサポートしているため、配列にはインターフェイスがデプロイされている必要があります。それを使用しての走査プロセスをIterator見てみましょう。Iterator

ここに画像の説明を挿入

図から次のことがわかります。

  1. Iteratorインターフェイスはnextメソッドを含むオブジェクトを返します。
  2. next を呼び出すたびに、データ構造の終わりを指すまで、配列内の項目が順番に返されます。
  3. value返された結果は、現在の値と現在の終了かどうかを含むオブジェクトです。done

オブジェクトを横断する

オブジェクトを走査してみると、以下に示すように、オブジェクトが反復可能ではないことが報告されていることがわかります。

ここに画像の説明を挿入

次に、上記の反復子オブジェクト ジェネレーターを使用して、オブジェクトがfor ofトラバーサルをサポートするようにすることができます。

obj[Symbol.iterator] = function* () {
    
    
  yield* this.name
}

ここに画像の説明を挿入

Object.keys()を使用してオブジェクトの値コレクションを取得しkey、それから使用することもできます。for of

const obj = {
    
    name: 'youhun',age: 18}
for(const key of Object.keys(obj)){
    
    
    console.log(key, obj[key])
    // name youhun
    // age 18
}

非同期反復

[Symbol.iterator]プロパティをデプロイする同期反復可能とは異なり、非同期反復可能には、[Symbol.asyncIterator]このプロパティがデプロイされたというマークが付けられます。

// 用生成器生成
const obj = {
    
    
  async *[Symbol.asyncIterator]() {
    
    
    yield 1;
    yield 2;
    yield 3;
  }
}

const asyncIterator = obj[Symbol.asyncIterator]()

asyncIterator.next().then(data => console.log(data)) // {value: 1, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 2, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 3, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: undefined, done: true}

これがasyncIterator非同期イテレータです。同期反復子iteratorとは異なり、 on メソッドasyncIteratorを呼び出すと、内部値がの形式でnextある Promise オブジェクトが取得されます{ value: xx, done: xx }Promise.resolve({ value: xx, done: xx })

なぜ非同期反復があるのでしょうか?

同期イテレータのデータ取得に時間がかかる場合 (実際のシナリオでのインターフェイスの要求など)、for-ofトラバーサルを使用すると問題が発生します。

const obj = {
    
    
  *[Symbol.iterator]() {
    
    
    yield new Promise(resolve => setTimeout(() => resolve(1), 5000))
    yield new Promise(resolve => setTimeout(() => resolve(2), 2000))
    yield new Promise(resolve => setTimeout(() => resolve(3), 500))
  }
}

console.log(Date.now())
for (const item of obj) {
    
    
    item.then(data => console.log(Date.now(), data))
}

// 1579253648926
// 1579253649427 3 // 1579253649427 - 1579253648926 = 501
// 1579253650927 2 // 1579253650927 - 1579253648926 = 2001
// 1579253653927 1 // 1579253653927 - 1579253648926 = 5001

これらはそれぞれitemインターフェース要求と見なすことができ、データが返されるまでの時間は必ずしも必要ではありません。上記の印刷結果は、データが処理される順序を制御できないという問題を示しています。

非同期イテレータをもう一度見てみましょう

const obj = {
    
    
  async *[Symbol.asyncIterator]() {
    
    
    yield new Promise(resolve => setTimeout(() => resolve(1), 5000))
    yield new Promise(resolve => setTimeout(() => resolve(2), 3000))
    yield new Promise(resolve => setTimeout(() => resolve(3), 500))
  }
}

console.log(Date.now())
for await (const item of obj) {
    
    
	console.log(Date.now(), item)
}

// 1579256590699
// 1579256595700 1 // 1579256595700 - 1579256590699 = 5001
// 1579256598702 2 // 1579256598702 - 1579256590699 = 8003
// 1579256599203 3 // 1579256599203 - 1579256590699 = 8504

非同期反復子は属性で宣言する必要があり、ループを[Symbol.asyncIterator]使用して処理されることに注意してください。for-await-of最終的な効果は、タスクを 1 つずつ処理し、前のタスクが処理されるのを待ってから次のタスクに進むことです。

したがって、非同期反復子は、データをすぐに取得できない状況に対処するために使用され、最終的な処理順序が走査順序と同じになるようにすることもできますが、順番に待つ必要があります。

待ちの

次のコードを使用してトラバースできます。

for await (const item of obj) {
    
    
  console.log(item)
}

つまり、非同期反復トラバーサルではfor-await-ofこのステートメントを使用する必要があります。非同期反復可能オブジェクトで使用するだけでなく、同期反復可能オブジェクトでも使用できます

const obj = {
    
    
  *[Symbol.iterator]() {
    
    
    yield 1
    yield 2
    yield 3
  }
}

for await(const item of obj) {
    
    
	console.log(item) // 1 -> 2 -> 3
}

[Symbol.asyncIterator]注:と が同時にオブジェクトにデプロイされている場合[Symbol.iterator]によって生成された非同期イテレータが最初に使用され[Symbol.asyncIterator]ます。for-await-ofこれは、もともと非同期イテレータのために生まれたものであるため、理解するのは簡単です。

逆に、2 つのイテレータが同時にデプロイされている場合は、for-or同期イテレータが最初に使用されます。

const obj = {
    
    
  *[Symbol.iterator]() {
    
    
    yield 1
    yield 2
    yield 3
  },
  async *[Symbol.asyncIterator]() {
    
    
    yield 4
    yield 5
    yield 6
  }
}

// 异步
for await(const item of obj) {
    
    
	console.log(item) // 4 -> 5 -> 6。优先使用由 [Symbol.asyncIterator] 生成的异步迭代器
}

// 同步
for (const item of obj) {
    
    
	console.log(item) // 1 -> 2 -> 3。优先使用由 [Symbol.iterator] 生成的同步迭代器
}

要約する

イテレータ ジェネレータのロジックは少し複雑かもしれませんが、その原理を理解することが非常に必要です。自分で書いてみると、その理由がわかります。この方法でのみ、必要な実装でオブジェクトをトラバースするための独自のイテレータを定義でき、実際の開発の対応するシーンにも適用できます。

おすすめ

転載: blog.csdn.net/youhunw/article/details/131703575