¿Qué es la iteración asíncrona? ¿Cómo personalizar la iteración? Una explicación detallada de los iteradores y generadores de ES6

¿Qué es la iteración asíncrona?  ¿Cómo personalizar la iteración?  Una explicación detallada de los iteradores y generadores de ES6

iterador

Los iteradores son una organización ordenada, secuencial y basada en extracción para consumir datos para controlar el comportamiento paso a paso.

En pocas palabras, iteramos y hacemos un bucle en un objeto iterable. En lugar de devolver todos los datos a la vez, llamamos a métodos relacionados para devolverlos en lotes.

Un iterador es un objeto que nos ayuda a recorrer una determinada estructura de datos, este objecttiene una nextfunción que devuelve un valor valuecon un doneatributo object, que apunta al valor definido valuepor la función actual en la secuencia de iteración .next

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

protocolo de iteración

El protocolo de iteración de ES6 se divide en protocolo iterador (protocolo iterador ) y protocolo iterable (protocolo iterable) , y los iteradores se implementan en función de estos dos protocolos.

Protocolo iterador: iterator协议 define valueuna forma estándar de generar secuencias. nextEste objeto es un iterador siempre que implemente las funciones requeridas . Todo un puntero para recorrer los elementos de la estructura de datos, similar a un cursor en una base de datos.

Protocolo iterable: una vez que se admite el protocolo iterable, significa que el objeto for-ofse puede usar para atravesar y se puede usar para definir o personalizar el comportamiento iterativo de los objetos JS. Los tipos incorporados comunes, como Array& Mapall, admiten el protocolo iterable. Un objeto debe implementar @@iteratorun método, lo que significa que el objeto debe tener una propiedad con una @@iterator keyconstante a la que se pueda Symbol.iteratoracceder.

Simular la implementación de un iterador

Basado en el protocolo iterador

// 实现
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())

Basado en un protocolo iterable

El objeto que implementa el método de generar un iterador se llama 可迭代对象Es decir, este objeto contiene un método que devuelve un objeto iterador

Generalmente utilizado Symbol.iteratorpara definir este atributo, el nombre científico se llama @@iteratormétodo

// 一个可迭代对象需要具有[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
}

En los dos ejemplos de iteradores simulados anteriores, todavía es relativamente complicado, pero ES6 presenta un objeto generador , que puede facilitar mucho el proceso de creación de objetos iteradores.

Constructor

Un generador es una función que devuelve un iterador , indicado por un asterisco (*****) después de la palabra clave, y la nueva palabra clave se usará en la función .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}

En el ejemplo anterior, creatIterator()el asterisco anterior * indica que es un generador, y el valor de retorno y el orden de retorno al yieldllamar al método iterador se especifican a través de palabras clave .next()

Cada vez que se ejecuta una declaración de rendimiento, la función dejará de ejecutarse automáticamente . Tome el ejemplo anterior como ejemplo, después de ejecutar la declaración , la función no ejecutará ningún otro idioma y la declaración no continuará yield 1hasta que se vuelva a llamar al método iterador .next()yield 2

Nota: yieldLas expresiones solo se pueden usar en las funciones del Generador y los errores se informarán cuando se usen en otros lugares.

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

Nota: ES6 no estipula functiondónde escribir el asterisco entre la palabra clave y el nombre de la función. Esto lleva a la siguiente escritura puede pasar.

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

Parámetro de paso del generador

yieldLa expresión en sí misma no devuelve un valor, o siempre devuelve undefined. nextEl método puede tomar un parámetro, que se utilizará como yieldvalor de retorno de la expresión anterior.

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())

inserte la descripción de la imagen aquí

Escenario de aplicación

En el desarrollo diario, cuando la siguiente interfaz depende de los datos de la interfaz anterior, el generador se puede utilizar sin tener en cuenta el problema del anidamiento del infierno de devolución de llamada asincrónica.

Simulación: obtenga datos de usuario después de 1 s, información de pedidos después de 2 s e información del producto después de 3 s

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()

inserte la descripción de la imagen aquí

para de

for ofEl ciclo puede obtener el valor clave en un par de valores clave , porque este ciclo está estrechamente relacionado con el iterador, así que hablemos de eso aquí.

Symbol.iteratorSe considera que una estructura de datos tiene una iteratointerfaz R y se puede usar siempre que tenga propiedades implementadas for ofy puede recorrer objetos iterables.

JavaScriptiterableEstructura de datos con interfaz por defecto :

  • matriz matriz
  • Mapa
  • Colocar
  • Cadena
  • Objeto de argumentos
  • Los objetos de lista de nodos, las matrices de clases y cualquier estructura de datos que implemente iteratoruna interfaz pueden usar el operador de distribución (...) de la matriz y operaciones como la desestructuración de asignaciones.

iterar sobre la matriz

Intente hacer un bucle en la matriz con for o

inserte la descripción de la imagen aquí

Dado que la matriz admite for...ofbucles, la matriz debe haber implementado Iteratorla interfaz, usémosla para ver Iteratorel proceso transversal de .

inserte la descripción de la imagen aquí

De la figura podemos ver:

  1. IteratorLa interfaz devuelve un nextobjeto con métodos.
  2. Cada llamada a next devuelve los elementos de la matriz por turnos hasta que apunta al final de la estructura de datos.
  3. El resultado devuelto es un objeto, que contiene el valor actual valuey si el final actualdone

atravesar objetos

Intente atravesar el objeto, encontraremos que informó que el objeto no es iterable, como se muestra a continuación

inserte la descripción de la imagen aquí

Luego, podemos usar el generador de objetos iterador anterior para hacer que el objeto también admita for ofel recorrido

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

inserte la descripción de la imagen aquí

También puede usar para Object.keys()obtener la colección de valores del objeto keyy luego usarfor of

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

iteración asíncrona

A diferencia de los iterables síncronos que implementan [Symbol.iterator]la propiedad, los iterables asíncronos se marcan con [Symbol.asyncIterator]esta propiedad implementada.

// 用生成器生成
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}

Aquí asyncIteratorestá el iterador asíncrono. iteratorA diferencia del iterador síncrono , asyncIteratorllamar nextal método obtendrá un objeto Promise cuyo valor interno tiene { value: xx, done: xx }la forma de, similar a Promise.resolve({ value: xx, done: xx }).

¿Por qué tener iteración asíncrona?

Si la adquisición de datos del iterador síncrono lleva tiempo (como solicitar una interfaz en un escenario real), entonces for-ofhabrá problemas si usa transversal.

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

Cada uno de estos puede considerarse itemcomo una solicitud de interfaz, y el tiempo para la devolución de datos no lo es necesariamente. Los resultados de impresión anteriores ilustran el problema: no podemos controlar el orden en que se procesan los datos.

Veamos nuevamente los iteradores asincrónicos.

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

Tenga en cuenta que el iterador asíncrono debe declararse en [Symbol.asyncIterator]el atributo y for-await-ofse procesará mediante el bucle. El efecto final es procesar las tareas una por una y esperar a que se procese la tarea anterior antes de ingresar a la siguiente tarea.

Por lo tanto, el iterador asíncrono se usa para lidiar con la situación en la que los datos no se pueden obtener de inmediato y también puede garantizar que el orden de procesamiento final sea igual al orden transversal, pero debe esperar en línea.

para-aguardar-de

Podemos usar el siguiente código para atravesar:

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

Es decir, el recorrido iterativo asincrónico necesita usar for-await-ofla declaración. Además de usarse en objetos iterables asíncronos, también se puede usar en objetos iterables síncronos .

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

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

[Symbol.asyncIterator]Nota: Si y se implementan en un objeto al mismo tiempo [Symbol.iterator], se usará primero [Symbol.asyncIterator]el iterador asíncrono generado por . Esto es fácil de entender, porque for-await-ofnació originalmente para iteradores asíncronos.

Por el contrario, si se implementan dos iteradores al mismo tiempo, pero for-orel iterador síncrono se usa primero.

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] 生成的同步迭代器
}

Resumir

La lógica del generador de iteradores puede ser un poco complicada, pero es muy necesario comprender su principio. Puedes intentar escribirlo tú mismo y saber por qué. Solo de esta manera la implementación requerida puede definir su propio iterador para atravesar el objeto, y también se puede aplicar en la escena correspondiente del desarrollo real.

Acho que você gosta

Origin blog.csdn.net/youhunw/article/details/131703575
Recomendado
Clasificación