Red de conocimiento informático - Conocimiento del nombre de dominio - Cómo utilizar JS para lograr una conversión asíncrona a síncrona

Cómo utilizar JS para lograr una conversión asíncrona a síncrona

Origen

Xiao Fei es un recién llegado que acaba de ingresar a la industria front-end. Debido a que se unió a una gran empresa, se ha convertido en un "gran dios" a sus ojos. Juniors y Juniors. Todos se conocieron. Me gusta preguntarle sobre los problemas de JS. No, en este momento apareció un mensaje como este en su QQ.

"Hola, ¿está el maestro aquí? Tengo una pregunta. para preguntar. Ahora hay algo como esto en nuestro código. Algo, pero no puedo obtener el resultado de retorno correcto 1234567function?getDataByAjax () {return?$.ajax(...postParam)}var?data = getDataByAjax() if?(data) {console.log(data.info)}

"Oh, estás haciendo una llamada asincrónica aquí. No puedes obtener el valor de retorno directamente. Tienes que escribir la declaración if en el función de devolución de llamada." Dijo Xiaofei sin pensar, para un fe 'profesional', esto no es un problema en absoluto.

"Pero espero simplemente transformar el método getDataByAjax para que se pueda establecer el siguiente código. "

"No tiene sentido estudiar esto. La sincronización es la esencia de js. La sincronización bloqueará las llamadas js y es muy lenta, pero si quieres insistir en ello una y otra vez, simplemente usa async. true"

"Como se espera de un maestro, lo intentaré inmediatamente cuando regrese."

Dos días después, se conectó a QQ con una cara triste

"Probé tu método. Pero no funciona en absoluto, estoy llorando~~”

“No te preocupes, déjame ver si los parámetros de tu postParam están bien” 123456{...dataType:?'jsonp',async:?true... }

"Esta es una solicitud jsonp. Es algo antiguo. No hay forma de sincronizar jsonp request."

"Sé que el principio de las solicitudes jsonp se implementa a través de etiquetas de script, pero, verá, el script también admite la sincronización, verá tags/attscriptasync.asp"

"Bueno, tal vez jquery no se haya implementado, jaja"

"Maestro, ¿puedes ayudarme a implementar un método de llamada sincrónica para jsonp, por favor (Xingxingyan)"

Aunque sí un poco sorprendido de por qué no se implementó jquery, pero como el estándar w3school está ahí, codificó dos líneas de código Nada, 1234567891011121314export?const loadJsonpSync = (url) => {var?result;?window.callback1 = (data) => (resultado = datos)let?head = window.document.getElementsByTagName('head')[0] let?js = window.document.createElement('script')?js.setAttribute('type',?'text/javascript ')?js.setAttribute('async',?'sync')?// Esta oración es obvia. La declaración enfatiza que src no se llama de forma asincrónica?js.setAttribute('src', url)?head.appendChild(js) return?result}

Bueno, ¡el resultado de la ejecución no está definido! El documento en realidad es inexacto y no me importa mucho, pensó Xiao Fei.

"Lo acabo de intentar y hay algún problema con el documento w3school. Este atributo asincrónico simplemente es incorrecto."

"Pero lo intenté una vez y confirmé que está bien" 12

(Los estudiantes interesados ​​pueden implementar los siguientes dos js y agregar la etiqueta async para probar).

"No puedo resolver esto", dijo Xiaofei con sarcasmo

La otra parte está desconectada

Resumen

Con respecto a esta pregunta, creo que no es solo Xiaofei, sino que muchas personas tienen dificultades para responderla. ¿Por qué ajax puede lograr la sincronización, pero jsonp no? Extendido a nodejs, ¿por qué readFile también puede lograr la sincronización (readFileSync), pero algunas bibliotecas no?

(En cuanto a la opción asíncrona del script, evitaremos hablar de ella por ahora porque la dimensión de conocimiento actual no es suficiente todavía, pero no te preocupes, a continuación se dará una explicación clara)

Ahora, abstraigamos esta pregunta desde una perspectiva informática:

¿Podemos convertir código asincrónico en código sincrónico? (ASYNCCALL => SYNCCALL)

Dado que es un problema abstracto, no podemos verlo desde la perspectiva de ingeniería/perspectiva de rendimiento/lenguaje de implementación, etc. (La sincronización es menos eficiente que la asincrónica), cada uno dimensión adicional, la complejidad crecerá exponencialmente.

Primero que nada, tengamos clara la definición de sincronización y asincronía en el campo de la informática.

Sincronización (inglés: Synchronization) se refiere al control de lo que sucede en un sistema La coordinación entre eventos conduce a la coherencia y unificación en el tiempo. La sincronización en el sistema también se denomina tiempo (sincrónica, sincronizada). --Extraído de la Enciclopedia Baidu

El concepto de asíncrono es relativo a la sincronización. Es decir, el tiempo es inconsistente y no está unificado.

Para aclarar este punto, podemos usar el diagrama de Gantt para expresar sincronización y asincronía.

Entre ellos, t1 y t2 son sincrónicos, y t1 y t3 son asincrónicos.

La respuesta se encuentra en el libro de texto universitario sobre principios del sistema operativo. Tenemos bloqueos de giro y semáforos para resolver el problema. El pseudocódigo es el siguiente: 1234567891011121314151617spinLock () {// Spin lock?fork Espera 3000 desbloqueo. ()? //Iniciar un hilo asincrónico y esperar tres segundos antes de ejecutar la acción de desbloqueo?¿bucle hasta desbloquear?//Realizar continuamente un bucle vacío hasta que la acción de desbloqueo Ponga 'desbloquear'}?//pv primitiva, ejecute el siguiente paso inmediatamente cuando el semáforo sea falso y establezca el semáforo en verdadero // De lo contrario, suspenda la pila de ejecución actual y colóquela en la cola de activación en espera // primitiva uv, establezca el semáforo en falso y active una pila de ejecución Semáforo () {? pv()?fork Espera 3000 uv()?pv()?uv()Put?'unlock'}

Muy bien, puedes encontrar la respuesta en el libro de texto de principios del sistema operativo. Entonces agregamos restricciones sobre esta base

Confiando solo en el propio js, ​​¿podemos convertir código asincrónico en código sincrónico? (ASYNCCALL => SYNCCALL)

Argumento

Con esta pregunta, echemos un vistazo al código fuente de jquery

src/ajax/xhr.js# L42

Se puede ver que el mecanismo de sincronización de ajax se implementa esencialmente mediante XMLHttpRequest, en lugar de la implementación nativa de js.

Por la misma razón, echemos un vistazo al código fuente de nodejs nuevamente

lib/fs.js#L550

Seguimiento desde readFileSync->tryReadSync- >readSync Si continúa, encontrará un enlace de C++, node_file.cc#L1167123456if?(req->IsObject()) {ASYNC_CALL(read, req, UTF8, fd, &uvbuf, 1, pos);}?else?{ SYNC_CALL(read, 0, fd, &uvbuf, 1, pos)args.GetReturnValue().Set(SYNC_RESULT);}

El secreto de la sincronización radica en la definición de macro de C++, que es un software de bajo nivel. Función de nivel implementada por el modo síncrono de C++.

Después de observar las dos llamadas asíncronas a síncronas más comunes, descubrimos que ninguna se implementó usando js.

Parece que js no puede ser compatible de forma nativa desde un nivel de fenómeno, pero esto no es suficiente. Exploramos la implementación de simulación de las características de bloqueo de giro/semáforo anteriores bajo la semántica de js (sé que te burlarás, =). =js en sí es de un solo subproceso, pero solo simula la función de subprocesos múltiples == Estoy completamente de acuerdo con esta oración, por lo que lo que se usa aquí no es la implementación, sino la implementación de simulación de funciones (Además, dado que settimeout tiene funciones de ejecución asincrónicas similares). para bifurcar, entonces usamos setitmeout para reemplazar temporalmente la bifurcación

Bloqueo de giro

1. La primera versión de implementación 1234567var?lock =?truesetTimeout(function?() {lock =?false} , 5000 )? while(lock);console.log('unlock')

Esperábamos ejecutar la declaración de desbloqueo después de 5000 ms, pero trágicamente, todo el proceso de Chrome se congeló.

Para explicar este problema claramente, leamos el modelo de bucle de eventos del maestro Ruan Yifeng

event-loop.html

Parece que tenemos una comprensión clara. de esto El bucle de eventos es la esencia de la secuencia de ejecución de js (el código de ejecución sincrónica se ejecuta inmediatamente, el código asincrónico se coloca en la cola de espera), luego, podemos dar la implementación de programación de js vm (una implementación del bucle de eventos) en base a esto Por supuesto, para explicar la falla del bloqueo de giro, solo es necesario simular operaciones asincrónicas, operaciones sincrónicas y bucles 123456789101112131415161718192021222324 //taskQueue: cola de tareas //runPart: tarea actualmente en ejecución (conjunto de instrucciones sincrónicas) //instruct: ¿función de instrucciones en ejecución? ? eventloop (taskQueue) {mientras(runPart = taskQueue.shift()) {mientras(instruir = runPart.shift()) {const {tipo, acto, codePart} = instructswitch(tipo) {caso?'SYNC':?console .log(act)if?(act ===?'loop')runPart.unshift({?act:?'loop',?type:?'SYNC'})breakcase?'ASYNC':?taskQueue.push( codePart)break} }}}

Luego convierta nuestra primera versión de spin lock 1234567891011121314151617181920let?taskQueue = [[{act:?'var lock = true', type:?'SYNC'},?// var lock = true{act:?'setTimeout',tipo:?'ASYNC',codePart: [{act:?'lock = false', tipo:?'SYNC'}]},?// setTimeout(función () { lock = false }, 5000)/*{act: 'loop',type: 'SYNC'},*/?// while(lock);{act:?'console.log(\'sync\')' , escriba:?'SYNC'}?// console.log('unlock')]]?

Pruébelo para cumplir con la definición de bucle evnet Y luego, al publicar los comentarios, bloqueamos con éxito todo el proceso de ejecución con el bucle, ¡y lock = false nunca tendrá la oportunidad de ejecutarse! ! !

(El mecanismo de programación real es mucho más complicado que esto. Si está interesado, puede echar un vistazo a la implementación de jscore en webkit ~~~)

Comprensión del principio , Mejoremos manualmente esta parte del código

2. Código mejorado 12345678910111213141516var?lock =?truesetTimeout(function?() {lock =?falseconsole.log('unlock')}, 5000)?function ? dormir() {var?i = 5000 while(i--);}?var?foo = () => setTimeout(función?() {dormir()bloquear && foo()})foo()

En esta versión de mejora, hemos realizado una acción de corte en while(true);. De hecho, esta técnica se usa ampliamente para mejorar la experiencia de la página. Por lo tanto, algunas personas se resisten porque el tiempo es impredecible.

¡Está mal negarse a utilizar settimeout!

6996528,

Prueba 1: reescribe eventloop y taskQueue para admitir el código mejorado

Sin embargo, si el foo() final del código se cambia a foo() && console.log('wait5sdo'),

Nuestro código aún falla, ¿por qué?

Presta atención a los lugares que marcamos en rojo, si has completado el cuestionario 1. , obtendrás el mismo orden que esta imagen

==Los fragmentos de código ejecutados de forma sincrónica deben ir antes que de forma asincrónica. ==

Entonces, independientemente de la teoría o la práctica, tenemos que admitir que en js, la propuesta de cambiar métodos asincrónicos a métodos sincrónicos es una imagen reflejada.

Oh, por cierto, Finalmente, necesitamos explicar el pozo que enterramos al principio, por qué async en jsonp no tuvo efecto. Es realmente fácil de explicar ahora, es decir, la acción de document.appendChild se completa con el hilo de renderizado dom, el llamado Lo que async bloquea es el análisis del dom, no el bloqueo del motor js. De hecho, después de que asíncrono obtiene recursos, la interacción con el motor js sigue siendo la acción de empujar taskQueue, que es lo que llamamos llamada asíncrona

Lectura recomendada: con respecto al análisis DOM, consulte WebKit Technology Insider No 9 Parte de carga de recursos del capítulo

Un giro repentino de los acontecimientos

Creo que muchos estudiantes modernos han comenzado a usar la sintaxis async/await. En la siguiente sintaxis, getAjax1 y la consola están sincronizados. características 1234async?function?() {var?data = await getAjax1()console.log(data)}

Después de hablar sobre la naturaleza del bucle de eventos y asíncrono, volvamos a examinar async/await.

Dios mío, este código se anula personalmente == Los fragmentos de código ejecutados de forma sincrónica deben preceder de forma asincrónica. ¡La regla de oro de ==!

Sorpresa o no, inesperado o no, esto existe en nuestro modelo al igual que los protones en los tres cuerpos. Hemos reexaminado el modelo anterior y no podemos encontrar ninguna laguna ni ningún punto que pueda revertirse, por lo que realmente tenemos que admitir que async / await es definitivamente una magia súper mágica.

En este punto tenemos que abandonar temporalmente la inferencia anterior y mirar este problema desde la perspectiva de async/await en sí.

Creo que mucha gente dirá que async/await es el azúcar sintáctico de CO, CO es el azúcar sintáctico de generador/promesa. Bien, entonces también podríamos eliminar esta capa de azúcar sintáctico y echar un vistazo a la esencia de este código. Hay demasiadas personas leyendo sobre CO. Realmente no puedo hablar de eso. Puedes leerlo. Después de leer este artículo, lo omitiremos directamente. Aquí hay una implementación simple

/5800210.html1234567891011121314151617181920function?wrap(wait) {var?iter?iter =. esperar()const f = () => {const { valor } = iter.next()valor && valor.entonces(f)}?f()}?función?*esperar() {var?p = () = >?new?Promise(resolve = > {?setTimeout(() => resolver(), 3000)})rendimiento?p()?console.log('unlock1')rendimiento?p()?console.log(' unlock2')?console.log( '¡¡Está sincronizado!!')}

Finalmente, encontramos la clave del problema si simplemente miramos el generador de espera (tenga en cuenta que no lo es). una función ordinaria), le resultará muy familiar. ¡Este es el pseudocódigo spinlock que propusimos originalmente! ! !

Esto ha sido completamente negado por nosotros. Es imposible que js tenga un bloqueo de giro. Si algo sale mal, debe haber un monstruo. Sí, el rendimiento y * son los monstruos que realizan magia async/await. .

Generador y rendimiento tienen significados literales. Gennerator se llama generador. El rendimiento es muy controvertido en varios círculos lingüísticos como Ruby, Python, JS, etc., pero la mayoría de la gente está de acuerdo con el concepto de "dar poder" (he visto la controversia en la lista de correo antes, pero el contenido específico Ya no puedo encontrarlo)

Lectura extendida: rendimiento del capítulo de cierre de metaprogramación de Ruby (rendimiento bajo la semántica de Ruby)

El llamado rendimiento significa que la CPU rendimiento durante la ejecución Uso de derechos, desde la perspectiva del sistema operativo, es la primitiva 'suspender'. Según la semántica de eventloop, parece almacenar temporalmente el bloque de código que se está ejecutando en ese momento (correspondiente a runPart en nuestro eventloop). y luego ejecute el siguiente bloque de programa secuencialmente.

Podemos modificar el bucle de eventos para implementar el mecanismo de transferencia de energía

Prueba 2 Modificar el bucle de eventos para admitir la primitiva de rendimiento

En este punto, se puede resolver modificando el modelo de eventloop. El problema, sin embargo, es que esto no se puede llamar mágico.

Un mundo armonioso

De hecho, a través de Babel, podemos degradar fácilmente para usar rendimiento (¡¡usando el concepto de rendimiento en el mundo es5!!)

Lo que parece imposible, ahora retomemos lo que se ha demostrado

==Los fragmentos de código ejecutados de forma sincrónica deben preceder a la ejecución asincrónica. == Según este teorema, realice una transformación inversa

==El código después de ejecutar el código asincrónico no debe ejecutarse de forma sincrónica (asincrónica).

==

Esta es una afirmación que todos en el círculo conocen, pero solo ahora se ha vuelto convincente (hemos llegado en un círculo largo)

Ahora, dejemos ¡Permita el uso de devolución de llamada para completar la misma función de un generador de espera sin usar generador/rendimiento! ! ! 1234567891011121314151617181920function?wait() {const p = () => ({valor:?new?Promise(resolve => setTimeout(() => resolver(), 3000))})let?state = {siguiente: () = > {state.next = programPartreturn?p()}}function?programPart() {?console.log('unlocked1')?state.next = programPart2return?p()}function?programPart2() {?console.log( 'unlocked2')?console.log('it\'s sync!!')return?{value: void 0}}return?state}

Genial, completamos con éxito la conversión de la función del generador (aunque el costo es alto), al mismo tiempo, este código en sí también explica la esencia del generador, función de orden superior, generador de fragmentos o simplemente llamado generador de funciones. Esto es exactamente igual que la traducción en scip, y también tiene su propio estado (máquina de estados finitos)

Se recomienda leer la parte del generador del Capítulo 1 de Construcción e interpretación de programas de computadora

Prueba 3 Práctica La solución que proporcionamos anteriormente tiene fallas, hable sobre ella desde la perspectiva del alcance

De hecho, sin saberlo, hemos reinventado la famosa transformación CPS en informática.

Continuación-passing_style

Finalmente, permítanme presentarles la herramienta regeneradora de conversión automática CPS de Facebook. Corrigió los defectos del alcance según nuestra base, haciendo que el generador sea natural y elegante en el mundo ES5. ¡Nos quitamos el sombrero ante Facebook! ! egenerator

Posdata

Se puede decir que la sincronización y la asincronía son los temas más comentados en todo el círculo. Sin embargo, después de muchas discusiones, parece que la mayoría de ellos se han convertido en lo más. -Llamado "nombre común convencional", todos quieren decir que mientras buscan nuevas tecnologías, no les importa cómo se heredan y desarrollan las nuevas tecnologías en tecnologías antiguas. Lo saben pero no saben por qué, y escriben js engañosos de acuerdo con lo que dicen otros. .

==La tecnología no debe ser impetuosa==

PD: La mayor contribución no es CO o Babel. El regenerador apareció varios meses antes que babel, y la implementación inicial se basó en esprima/recast. China no parece saber mucho sobre resprima/recast. De hecho, cuando babel acaba de nacer, esprima/esprima-fb/acron y recast. /jstransfrom/babel-generator Varias familias importantes han tenido una feroz lucha en torno a reaccionar. Tal vez algún día en el futuro, hable sobre por qué Babel ríe último en términos de detalles de implementación~~~~