Historia de la programación asincrónica de JavaScript del marco de interfaz de usuario web Javascript

En las primeras aplicaciones web, al interactuar con el fondo, es necesario enviar el formulario y luego brindarle al usuario los resultados de comentarios después de actualizar la página. Durante el proceso de actualización de la página, el fondo devolverá un fragmento de código HTML. La mayor parte del contenido de este HTML es básicamente el mismo que el de la página anterior, lo que inevitablemente provocará una pérdida de tráfico y también prolongará el tiempo de respuesta de la página. Hará que la gente sienta que la experiencia de las aplicaciones web no es tan buena como la de las aplicaciones cliente.

En 2004 nació AJAX, o tecnología "Asynchronous JavaScript and XML", que mejoró la experiencia de las aplicaciones web. En 2006, apareció jQuery, elevando la experiencia de desarrollo de aplicaciones web a un nuevo nivel.

Debido a la característica de subproceso único del lenguaje JavaScript, ya sea la activación de eventos o AJAX, las tareas asincrónicas se activan mediante devoluciones de llamada. Si queremos procesar múltiples tareas asincrónicas linealmente, aparecerá la siguiente situación en el código:

getUser(token, función (usuario) {

getClassID(usuario, función (id) {

getClassName(id, function (name) {

  console.log(name)

})

})

})

A menudo nos referimos a este tipo de código como: "infierno de devolución de llamada".

Eventos y devoluciones de llamadas

Como todos sabemos, el tiempo de ejecución de JavaScript se ejecuta en un solo subproceso y activa tareas asincrónicas según el modelo de eventos. No es necesario considerar el problema del bloqueo de la memoria compartida y los eventos vinculados se activarán en orden. Para comprender las tareas asincrónicas de JavaScript, primero debemos comprender el modelo de eventos de JavaScript.

Debido a que es una tarea asincrónica, necesitamos organizar un fragmento de código para ejecutarlo en el futuro (cuando finalice el tiempo especificado o cuando se active un evento), generalmente colocamos este fragmento de código en una función anónima, generalmente llamada devolución de llamada. función.

setTimeout(función() {

// La devolución de llamada se activa cuando finaliza el tiempo especificado

}, 800)

window.addEventListener(“cambiar tamaño”, función() {

// La devolución de llamada se activa cuando cambia la ventana del navegador

})

carrera futura

Como se mencionó anteriormente, la operación de la función de devolución de llamada es en el futuro, lo que significa que las variables utilizadas en la devolución de llamada no se fijan en la etapa de declaración de devolución de llamada.

para (var i = 0; i < 3; i++) {

setTimeout(función() {

console.log("i =", i)

}, 100)

}

Aquí se declaran tres tareas asincrónicas en sucesión, y el resultado de la variable i se generará después de 100 milisegundos. Según la lógica normal, se deben generar los tres resultados de 0, 1 y 2.

Sin embargo, este no es el caso. Este es también el problema que encontraremos cuando entremos en contacto por primera vez con JavaScript, porque el tiempo de ejecución real de la función de devolución de llamada es en el futuro, por lo que el valor de salida de i es el valor en el final del ciclo, y las tres tareas asincrónicas El resultado es consistente y se generarán tres i = 3.
inserte la descripción de la imagen aquí
Los estudiantes que han experimentado este problema generalmente saben que podemos resolverlo cerrando o redeclarando variables locales.

cola de eventos

Una vez vinculado el evento, se almacenarán todas las funciones de devolución de llamada y luego, durante el proceso de ejecución, otro subproceso programará y procesará estas devoluciones de llamada llamadas de forma asincrónica. Una vez que se cumpla la condición "desencadenante", la función de devolución de llamada se colocará en el correspondiente cola de eventos (aquí se entiende simplemente como una cola, en realidad hay dos colas de eventos: macrotarea y microtarea).

Las condiciones desencadenantes generalmente se cumplen en las siguientes situaciones:

Cooperación estratégica oficial y co-construcción de HarmonyOS —— Comunidad tecnológica de HarmonyOS

Eventos desencadenados por operaciones relacionadas con DOM, como hacer clic, mover, desenfocar, etc.;

Operaciones relacionadas con IO, finalización de lectura de archivos, finalización de solicitudes de red, etc.;

Operaciones relacionadas con el tiempo, alcanzando el tiempo acordado de la tarea programada;

Cuando ocurren los comportamientos anteriores, la función de devolución de llamada especificada previamente en el código se colocará en una cola de tareas. Una vez que el hilo principal esté inactivo, las tareas que contiene se ejecutarán una por una de acuerdo con el proceso de primero en entrar, primero en salir. . Cuando se activa un nuevo evento, se vuelve a colocar en la devolución de llamada, y así sucesivamente, por lo que este mecanismo de JavaScript generalmente se denomina "mecanismo de bucle de eventos".

para (var i = 1; i <= 3; i++) {

constante x = yo

setTimeout(función() {

console.log(`第${x}个setTimout被执行`)

}, 100)

}

Se puede ver que la secuencia de ejecución satisface la característica de primero en entrar, primero en salir de la cola y la primera declaración se ejecuta primero.
inserte la descripción de la imagen aquí
bloqueo de hilo

Debido a las características de un solo subproceso de JavaScript, los temporizadores en realidad no son confiables. Cuando el código encuentra una situación de bloqueo, incluso si el evento alcanza el tiempo de activación, esperará hasta que el subproceso principal esté inactivo antes de ejecutarse.

inicio constante = Fecha.ahora()

setTimeout(función() {

consola.log( 实际等待时间: ${Date.now() - start}ms)

}, 300)

// El bucle while bloquea el hilo durante 800 ms

while(Fecha.ahora() - inicio < 800) {}

En el código anterior, la función de devolución de llamada se activa después de que el temporizador se configura en 300 ms. Si el código no está bloqueado, generará el tiempo de espera después de 300 ms en circunstancias normales.

Pero no hemos agregado un bucle while, y este bucle finalizará después de 800 ms. Este bucle ha bloqueado el hilo principal aquí, lo que hace que la función de devolución de llamada no se ejecute normalmente cuando se acabe el tiempo.
inserte la descripción de la imagen aquí
Promesas

La forma de devolución de llamada de eventos es particularmente fácil de provocar un infierno de devolución de llamada durante el proceso de codificación. Y Promise proporciona una forma más lineal de escribir código asincrónico, algo similar al mecanismo de canalización.

// infierno de devolución de llamada

getUser(token, función (usuario) {

getClassID(usuario, función (id) {

getClassName(id, function (name) {

  console.log(name)

})

})

})

// Promesa

getUser(token).luego(función (usuario) {

devolver getClassID (usuario)

}).entonces(función (id) {

devolver getClassName(id)

}).entonces(función (nombre) {

consola.log(nombre)

}).catch(función (err) {

console.error('solicitar excepción', err)

})

Promise tiene implementaciones similares en muchos idiomas. Durante el desarrollo de JavaScript, los marcos más famosos jQuery y Dojo también implementaron implementaciones similares. En 2009, se lanzó la especificación CommonJS, basada en la implementación de Dojo.Deffered, y se propuso la especificación Promise/A. También fue este año que nació Node.js. Muchas implementaciones de Node.js se basan en la especificación CommonJS, y la más familiar es su esquema de modularización.

El objeto Promise también se implementó en los primeros Node.js, pero en 2010, Ry (el autor de Node.js) pensó que Promise era una implementación de nivel relativamente alto y que el desarrollo de Node.js originalmente se basaba en el motor V8. , el soporte Promise del motor V8 no se proporcionó de forma nativa, por lo que los módulos posteriores de Node.js utilizaron el estilo de devolución de llamada de error primero (cb (error, resultado)).

constante fs = requerir('fs')

// El primer parámetro es el objeto Error, si no está vacío, significa que ocurrió una excepción

fs.readFile('./README.txt', función (err, búfer) {

si (errar! == nulo) {

return

}

consola.log(buffer.toString())

})

Esta decisión también provocó la aparición de varias bibliotecas Promise en Node.js, siendo las más famosas Q.js y Bluebird. Respecto a la implementación de Promise, escribí un artículo antes, si estás interesado puedes leerlo: "Enseñandote a realizar la promesa".

Antes de Node.js@8, la implementación nativa de Promise de V8 tenía algunos problemas de rendimiento, lo que hacía que el rendimiento de Promise nativo fuera incluso inferior al de algunas bibliotecas de Promise de terceros.
inserte la descripción de la imagen aquí
Por lo tanto, en proyectos Node.js de versión baja, Promise a menudo se reemplaza globalmente:

const Bulebird = requerir('pájaro azul')

global.Promise = Bulebird

Generador y compañía

Generador (generador) es un nuevo tipo de función proporcionado por ES6, que se utiliza principalmente para definir una función que puede iterarse a sí misma. Se puede construir una función Generador a través de la sintaxis de la función *. Después de ejecutar la función, se devolverá un objeto de iteración (iterador). Este objeto tiene un método next(). Cada vez que se llama al método next(), haga una pausa antes de la palabra clave de rendimiento hasta que vuelva a llamar al método next().

función * para cada uno (matriz) {

constante len = matriz.longitud

for (sea i = 0; i < len; i ++) {

yield i;

}

}

constante = para cada uno ([2, 4, 6])

it.next() // { valor: 2, hecho: falso }

it.next() // { valor: 4, hecho: falso }

it.next() // { valor: 6, hecho: falso }

it.next() // { valor: indefinido, hecho: verdadero }

El método next() devolverá un objeto, que tiene dos atributos valor y listo:

valor: Indica el valor después del rendimiento;

hecho: Indica si la función ha sido ejecutada;

Dado que la función generadora tiene las características de ejecución de interrupción, la función generadora se considera como un contenedor de operación asincrónica, y luego el método del objeto Promise se puede usar para transferir el derecho de ejecución de la lógica asincrónica, y se agrega un objeto Promise después de cada rendimiento, el iterador se puede ejecutar de forma continua.

función * gen(token) {

usuario constante = rendimiento getUser(token)

const cId = rendimiento getClassID (usuario)

nombre constante = rendimiento getClassName(cId)

consola.log(nombre)

}

const g = gen('token-xxxx')

// Ejecuta el valor devuelto por el siguiente método como un objeto Promise

const {valor: promesa1} = g.next()

promesa1.entonces(usuario => {

// El valor pasado en el segundo método next será aceptado por la variable antes de la primera palabra clave de rendimiento en el generador

// Lo mismo ocurre con el retroceso, la variable aceptará el valor del tercer método siguiente antes del segundo rendimiento

// Sólo se descartará el valor del primer método siguiente

const {valor: promesa2} = gen.next(usuario).valor

promesa2.entonces(cId => {

const { value: promise3, done } = gen.next(cId).value

// 依次先后传递,直到 next 方法返回的 done 为 true

})

})

Resumamos la lógica anterior, de modo que después de que cada objeto Promise regrese normalmente, llamará automáticamente a continuación y dejará que el iterador se ejecute solo hasta que se complete la ejecución (es decir, hecho es verdadero).

función co(gen,…args) {

const g = gen(…argumentos)

función siguiente (datos) {

const { value: promise, done } = g.next(data)

if (done) return promise

promise.then(res => {

  next(res) // 将 promise 的结果传入下一个 yield

})

}

next() // inicia la autoejecución

}

co(gen, 'token-xxxx')

Esta es la lógica de implementación de la biblioteca central inicial co de koa, pero co ha realizado cierta verificación de parámetros y manejo de errores. Agregar co al generador puede hacer que el proceso asincrónico sea más simple y fácil de leer, lo que definitivamente es algo feliz para los desarrolladores.

asíncrono/espera

Se puede decir que async/await es la solución de la transformación asincrónica de JavaScript. De hecho, es esencialmente un azúcar sintáctico de Generator & co. Solo necesita agregar async antes de la función del generador asincrónico y luego reemplazar el rendimiento en la función del generador. con espera.

función asíncrona divertida (token) {

usuario constante = esperar getUser(token)

const cId = espera getClassID (usuario)

nombre constante = esperar getClassName(cId)

consola.log(nombre)

}

divertido()

La función asíncrona tiene un autoejecutor incorporado, y await no se limita a un objeto Promise, que puede ser cualquier valor, y la semántica de async/await es más clara que el rendimiento del generador, y se puede entender De un vistazo, se trata de una operación asincrónica.

Fuente del artículo: los derechos de autor de la red pertenecen al autor original.

El contenido anterior no tiene fines comerciales, si involucra problemas de propiedad intelectual, comuníquese con el editor, lo solucionaremos de inmediato.

Supongo que te gusta

Origin blog.csdn.net/xuezhangmen/article/details/132017174
Recomendado
Clasificación