Lenguaje compilado a JS: la forma más elegante de hacer esperas sincrónicas


8

Estoy tratando de hacer (otro idioma) que se compila a JavaScript. Una de las características que me gustaría tener es la capacidad de realizar las operaciones asíncronas de JavaScript sincrónicamente (no exactamente sincrónicamente, sin bloquear el hilo principal, por supuesto). Menos charla, más ejemplos:

/* These two snippets should do exactly the same thing */

// the js way
var url = 'file.txt';
fetch(url)
    .then(function (data) {
        data = "(" + data + ")";
        return sendToServer(data);
    }).then(function (response) {
        console.log(response);
    });

// synchronous-like way
var url = 'file.txt';
var response = sendToServer("(" + fetch(url) + ")");
console.log(response);

¿Cuál es la forma más elegante de volver a compilarlo en JS?

Los criterios, ordenados por importancia:

  1. Actuación
  2. Compatibilidad al revés
  3. Legibilidad compilada del código (no es realmente importante)

Hay varias formas posibles de implementarlo, esos tres me vinieron a la mente:

  1. Usando promesas:

    • f(); a( b() ); se convertiría en
    • f().then(function(){ return b() }).then(function(x){ a(x) });
    • Pros: relativamente simple de implementar, polifilizable de vuelta a ES3
    • contras: crear una nueva función y una instancia de Proxy para cada llamada de función
  2. Usando generadores :

    • f(); a( b() ); se convertiría en
    • yield f(); tmp = yield b(); yield a( tmp );
    • pros: javascript más agradable
    • contras: crear proxies, generadores e iteradores en cada paso, tampoco polifilables
    • EDITAR: de hecho, son polifilables usando otro recompilador (por ejemplo, Regenerator )
  3. Usando XMLHttpRequest sincrónico:

    • solicite un script de servidor que espere hasta otra llamada de otro lugar en el código
    • pros: de facto no hay cambios en el código, súper fácil de implementar
    • contras: requiere script del servidor (=> no portabilidad, solo navegador); Además, XMLHttpRequest síncrono bloquea el hilo para que no funcione en absoluto
    • EDITAR: en realidad funcionaría dentro de un trabajador

El principal problema es que no se puede saber si una función es asíncrona hasta el tiempo de ejecución. Eso da como resultado que las dos soluciones que al menos funcionan sean mucho más lentas que el JS puro. Entonces pregunto:

¿Hay mejores formas de implementar?

De las respuestas:

Aguardar palabra clave (@ MI3Guy)

  • C # way (que es bueno !)
  • introduce una nueva palabra clave (que se puede disfrazar como una función (ciudadano de primera clase) que, por ejemplo, arroja si el programador intenta pasarla como argumento)
  • ¿Cómo saber que la función es asíncrona? No hay otras palabras clave!
    • ¿Cada función que contiene la awaitpalabra clave se vuelve asíncrona?
    • Cuando llega el código await, ¿devuelve una promesa? ¿Otra cosa se trata como una función ordinaria?

11
synchronously (without blocking the thread, of course) Usted sigue usando esa palabra. No creo que signifique lo que crees que significa, porque sincrónicamente significa "bloquear el hilo".
Mason Wheeler


1
@MasonWheeler: supongo que la operación significa que quiere usar la sintaxis de estilo sincrónico.
Brian

No llamaré a esto una respuesta, pero lo que estás intentando parece un poco a lo que hace C # con sus funciones asíncronas. Siento que el método más consistente con valores de retorno sería el uso de promesas, y sí, esto implicaría muchas funciones anónimas. Podría intentar encontrar artículos sobre cómo el código C # se trans-compila para referencia; Es probable que sea igual de ineficiente en algunos aspectos.
Katana314

2
f(); a( b() );se convertiría en f().then(b).then(a);No incluye funciones.
thefourtheye

Respuestas:


3

Suponiendo que el idioma se escribe dinámicamente, puedo ver dos formas diferentes de manejar esto sin saber en tiempo de compilación si una llamada es asíncrona.

Un enfoque sería asumir que cada llamada podría ser asíncrona. Sin embargo, esto sería increíblemente ineficiente y produciría mucho código extra. Esto es esencialmente lo que está haciendo en la opción 2.

Otra opción es usar fibras. Las fibras son como hilos livianos programados por el programa. La fibra misma decide cuándo renunciar al control. Sin embargo, estos requieren soporte del sistema operativo. Por lo que puedo decir, la única forma de usar fibras en JavaScript es dentro de node.js. Mi conjetura es que si está compilando para JS que el objetivo (o al menos un objetivo) es ejecutarse en el navegador que descarta esta opción. Esta sería una versión más ligera de la opción 3.

Honestamente, consideraría agregar una palabra clave como C # 's await. Esto tiene una serie de ventajas además de especificar que una función es asíncrona. Se pueden llamar funciones para producir futuros sin esperarlos de inmediato. Esto permite que se realicen múltiples operaciones asincrónicas a la vez en lugar de en secuencia. Además, es mejor ser explícito sobre qué operaciones son asíncronas porque se puede ejecutar otro código mientras se lleva a cabo la operación. Si async es automático, puede ser sorprendente cuando algunos datos que está utilizando son modificados por un código externo.

En C # dentro de un método marcado como asíncrono, la instrucción return se usa para devolver el valor que estará dentro del futuro, no en el futuro mismo. Sin embargo, tenga en cuenta que el modificador asíncrono no es realmente parte de la firma del método. awaitse puede usar con cualquier método que devuelva un futuro.

Si no desea agregar un modificador asíncrono para las funciones, puede decidir (como usted dijo) que cualquier función con una espera se trata como si tuviera un modificador asíncrono implícito. Entonces, incluso si una función tiene un await, pero regresa antes de alcanzarlo, la función aún debería devolver un futuro. También recomendaría exponer alguna forma de convertir un valor en un futuro completo que contenga el valor, especialmente si no hay forma de hacerlo implícitamente agregando el modificador asíncrono.


Mi objetivo original era crear un lenguaje totalmente consistente sin estructuras que no pudieran reutilizarse (por ejemplo, si existe if(){} else{}, el programador también podría definir una estructura similar ifZero(){} elseWhile(){}). Pero parece que tengo que introducir una función de espera global que arroja (o al menos devuelve indefinido) si el programador intenta asignar su valor a una variable (las funciones son ciudadanos de primera clase).
m93a

Se actualizó la pregunta (la parte inferior). ¿Podría decirme qué piensa de las preguntas allí? Gracias.
m93a

Agregué más detalles basados ​​en sus actualizaciones. Avísame si quisiste decir algo diferente.
MI3Guy
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.