Gerador ES6, um estilo de expressão de controle de fluxo assíncrono aparentemente síncrono

Este artigo foi compartilhado pela Huawei Cloud Community " Semana de leitura de março·JavaScript que você não conhece | Gerador ES6, um estilo de expressão de controle de processo assíncrono aparentemente síncrono ", autor: Ye Yiyi.

Construtor

interromper a corrida completa

Há uma suposição na qual os desenvolvedores de JavaScript confiam quase universalmente em seu código: uma vez que uma função começa a ser executada, ela será executada até o fim e nenhum outro código poderá interrompê-la e inseri-la no meio.

ES6 introduz um novo tipo de função que não está em conformidade com esse recurso de execução até o fim. Este novo tipo de função é chamado de gerador.

var x = 1;

função foo() {
  x++;
  bar(); // <- esta linha é executada entre as instruções x++ e console.log(x)
  console.log('x:', x);
}

barra de função() {
  x++;
}

foo(); // x: 3

O que acontece se bar() não estiver lá? Obviamente o resultado será 2, não 3. O resultado final é 3, então bar() será executado entre x++ e console.log(x).

Mas o JavaScript não é preemptivo, nem é multithread (ainda). No entanto, ainda seria possível implementar tais interrupções de maneira cooperativa (simultaneidade) se o próprio foo() pudesse de alguma forma indicar uma pausa neste ponto do código.

Aqui está o código ES6 para implementar a simultaneidade cooperativa:

var x = 1;

função*foo(){
  x++;
  rendimento; // Pausa!
  console.log('x:', x);
}

barra de função() {
  x++;
}
//Constrói um iterador para controlar este gerador
var it = foo();

//Inicie foo() aqui!
it.next();
console.log('x:', x); //2
bar();
console.log('x:', x); //3
it.next(); // x: 3
  • A operação it = foo() não executa o gerador *foo(), mas apenas constrói um iterador (iterador), que controla sua execução.
  • *foo() faz uma pausa na instrução yield, ponto em que a primeira chamada it.next() termina. Neste ponto *foo() ainda está em execução e ativo, mas em estado suspenso.
  • A chamada it.next() final retoma a execução do gerador *foo() de onde foi pausado e executa a instrução console.log(..), que usa o valor atual de x, 3.

Um gerador é um tipo especial de função que pode ser iniciado e parado uma ou mais vezes, mas não precisa necessariamente ser concluído.

entrada e saída

Uma função geradora é uma função especial que ainda possui algumas propriedades básicas de funções. Por exemplo, ele ainda pode aceitar parâmetros (ou seja, entrada) e retornar valores (ou seja, saída).

função* foo(x, y) {
  retornar x * y;
}

var it = foo(6, 7);
var res = it.next();

res.valor; //42

Passe os parâmetros reais 6 e 7 para *foo(..) como parâmetros xey respectivamente. *foo(..) retorna 42 para o código de chamada.

vários iteradores

Cada vez que você constrói um iterador, você na verdade constrói implicitamente uma instância do gerador.É a instância do gerador que é controlada por meio deste iterador.

Várias instâncias do mesmo gerador podem ser executadas simultaneamente e podem até interagir entre si:

função*foo(){
  var x = rendimento 2;
  z++;
  var y = rendimento x * z;
  console.log(x, y, z);
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().valor; // 2 <-- rendimento 2
var val2 = it2.next().valor; // 2 <-- rendimento 2

val1 = it1.next(val2 * 10).valor; // 40 <-- x:20, z:2
val2 = it2.next(val1 * 5).valor; // 600 <-- x:200, z:3

it1.next(val2/2); // s:300
//203003
it2.next(val1/4); // s:10
//200 10 3

Resuma brevemente o processo de execução:

(1) Duas instâncias de *foo() são iniciadas ao mesmo tempo, e as duas next() obtêm o valor 2 da instrução yield 2, respectivamente.

(2) val2 * 10, que é 2 * 10, é enviado para a primeira instância do gerador it1, então x obtém o valor 20. z aumenta de 1 para 2, então 20 * 2 é emitido via rendimento, definindo val1 como 40.

(3) val1 * 5, que é 40 * 5, é enviado para a segunda instância do gerador it2, então x obtém o valor 200. z é incrementado de 2 para 3 novamente, então 200 * 3 é emitido via rendimento, definindo val2 como 600.

(4) val2/2, que é 600/2, é enviado para a primeira instância do gerador it1, então y obtém o valor 300 e, em seguida, os valores de xyz são impressos como 20300 3 respectivamente.

(5) val1/4, que é 40/4, é enviado para a segunda instância do gerador it2, então y obtém o valor 10 e, em seguida, os valores de xyz são impressos como 200 10 3 respectivamente.

Gerador produz valor

Produtores e Iteradores

Suponha que você queira gerar uma sequência de valores, cada um com uma relação específica com o anterior. Conseguir isso requer um produtor com estado que possa lembrar o último valor que produziu.

Um iterador é uma interface bem definida para obter uma sequência de valores de um produtor passo a passo. A interface do iterador JavaScript é chamar next() toda vez que você deseja obter o próximo valor do produtor.

A interface do iterador padrão pode ser implementada para geradores de sequência numérica:

var alguma coisa = (função () {
  var próximoValido;

  retornar {
    // loop for..of necessário
    [Symbol.iterador]: função () {
      devolva isso;
    },

    // Método de interface do iterador padrão
    próximo: função () {
      if (nextVal === indefinido) {
        próximoVal = 1;
      } outro {
        próximoVal = 3 * próximoVal + 6;
      }

      return {feito: falso, valor: nextVal};
    },
  };
})();

algo.próximo().valor; //1
algo.próximo().valor; //9
algo.próximo().valor; //33
algo.próximo().valor; //105

A chamada next() retorna um objeto. Este objeto possui duas propriedades: done é um valor booleano que identifica o status de conclusão do valor do iterador que coloca o valor da iteração;

iterável

iterável é um objeto que contém um iterador que pode ser iterado sobre seus valores.

Começando com ES6, a maneira de extrair um iterador de um iterável é que o iterável deve suportar uma função cujo nome seja o valor de símbolo ES6 especializado, Symbol.iterator. Quando esta função é chamada, ela retorna um iterador. Normalmente cada chamada retorna um novo iterador, embora isso não seja obrigatório.

var a = [1, 3, 5, 7, 9];

para (var v de a) {
  console.log(v);
}
// 1 3 5 7 9

a no trecho de código acima é iterável. O loop for..of chama automaticamente sua função Symbol.iterator para construir um iterador.

for (var v de alguma coisa) {
  ..
}

O loop for..of espera que algo seja iterável, então ele procura e chama sua função Symbol.iterator.

iterador gerador

O gerador pode ser considerado um produtor de valores. Extraímos um valor de cada vez por meio da chamada next() da interface do iterador.

Os próprios geradores não são iteráveis. Quando você executa um gerador, você obtém um iterador:

função *foo(){ .. }

var it = foo();

O anterior produtor de sequência numérica infinita pode ser implementado através de um gerador, semelhante a este:

função* alguma coisa() {
  var próximoValido;

  enquanto (verdadeiro) {
    if (nextVal === indefinido) {
      próximoVal = 1;
    } outro {
      próximoVal = 3 * próximoVal + 6;
    }

    rendimento próximoVal;
  }
}

Como o gerador faz uma pausa a cada rendimento, o estado (escopo) da função *something() é mantido, o que significa que nenhum fechamento é necessário para manter o estado da variável entre as chamadas.

Gerador de iterador assíncrono

função foo(x, y) {
  ajax('http://some.url.1/? x=' + x + '&y=' + y, function (err, dados) {
    se (errar) {
      // Lança um erro para *main()
      it.throw(err);
    } outro {
      //Restaura *main() com os dados recebidos
      it.next(dados);
    }
  });
}

função* principal() {
  tentar {
    var texto = rendimento foo(11, 31);
    console.log(texto);
  } pegar (errar) {
    console.error(erro);
  }
}

var it = main();

//Começa aqui!
it.next();

No rendimento foo(11,31), foo(11,31) é chamado primeiro, o que não retorna nenhum valor (ou seja, retorna indefinido), então uma chamada é feita para solicitar os dados, mas o que realmente é feito depois é rendimento indefinido.

O rendimento não é usado aqui no sentido de passagem de mensagens, mas apenas para controle de fluxo para implementar pausa/bloqueio. Na verdade, ainda haverá passagem de mensagem, mas será apenas uma passagem de mensagem unilateral após o gerador retomar a operação.

Dê uma olhada em foo(..). Se esta solicitação Ajax for bem-sucedida, chamamos:

it.next(dados);

Isso retoma o gerador com os dados de resposta, o que significa que a expressão de rendimento pausada recebeu esse valor diretamente. Este valor é então atribuído ao texto da variável local à medida que o código gerador continua a ser executado.

Resumir

Vamos resumir o conteúdo principal deste artigo:

  • Generator é um novo tipo de função no ES6. Nem sempre é executado até o fim como as funções comuns. Em vez disso, um gerador pode ser pausado durante a execução (preservando totalmente seu estado) e retomado de onde foi pausado posteriormente.
  • O par yield/next(..) não é apenas um mecanismo de controle, mas também um mecanismo bidirecional de passagem de mensagens. colheita .. A expressão essencialmente pausa e espera por um valor, e a chamada next(..) subsequente retorna um valor (ou implicitamente indefinido) para a expressão de rendimento pausada.
  • A principal vantagem dos geradores quando se trata de fluxo de controle assíncrono é que o código dentro do gerador é uma sequência de etapas que expressam uma tarefa de maneira naturalmente síncrona/sequencial. O truque é esconder a possível assincronia por trás da palavra-chave yield e mover a assincronia para a parte do código que controla o iterador do gerador.
  • Os geradores mantêm um padrão de código sequencial, síncrono e de bloqueio para código assíncrono, o que permite ao cérebro seguir o código com mais naturalidade, resolvendo uma das duas principais falhas da assincronia baseada em retorno de chamada.

 

Clique para seguir e conhecer as novas tecnologias da Huawei Cloud o mais rápido possível~

 

A primeira grande atualização de versão do JetBrains 2024 (2024.1) é de código aberto. Até a Microsoft planeja pagar por isso. Por que ainda está sendo criticado por ser de código aberto? [Recuperado] O back-end do Tencent Cloud travou: um grande número de erros de serviço e nenhum dado após o login no console. A Alemanha também precisa ser "controlável de forma independente". O governo estadual migrou 30.000 PCs do Windows para o Linux deepin-IDE e finalmente conseguiu inicialização! O Visual Studio Code 1.88 foi lançado. Bom rapaz, a Tencent realmente transformou o Switch em uma "máquina de aprendizagem pensante". A área de trabalho remota RustDesk inicia e reconstrói o cliente Web. O banco de dados de terminal de código aberto do WeChat baseado em SQLite, WCDB, recebeu uma grande atualização.
{{o.nome}}
{{m.nome}}

Acho que você gosta

Origin my.oschina.net/u/4526289/blog/11051652
Recomendado
Clasificación