Estou tentando criar (ainda outra) linguagem que compila para JavaScript. Um dos recursos que eu gostaria de ter é a capacidade de executar operações assíncronas do JavaScript de forma síncrona (não exatamente síncrona - sem bloquear o segmento principal, é claro). Menos conversas, mais exemplos:
/* 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);
Qual é a maneira mais elegante de compilá-lo de volta ao JS?
Os critérios, classificados por importância:
- atuação
- Compatibilidade com versões anteriores
- Legibilidade do código compilado (não é realmente importante)
Existem várias maneiras possíveis de implementá-lo, essas três me vieram à mente:
Usando promessas:
f(); a( b() );
se transformaria emf().then(function(){ return b() }).then(function(x){ a(x) });
- profissionais: relativamente simples de implementar, polifillable de volta ao ES3
- contras: criar uma nova função e uma instância de proxy para cada chamada de função
-
f(); a( b() );
se transformaria emyield f(); tmp = yield b(); yield a( tmp );
- pros: melhor javascript
- contras: criação de proxies, geradores e iteradores em todas as etapas, também não polifillable
- EDIT: na verdade, eles são polyfillable usando outro recompilador (por exemplo, Regenerator )
Usando XMLHttpRequest síncrono:
- solicitar um script de servidor que aguarde até outra chamada de outro lugar no código
- prós: de fato, nenhuma alteração no código, super fácil de implementar
- contras: requer script do servidor (=> sem portabilidade, somente navegador); além disso, XMLHttpRequest síncrono bloqueia o encadeamento para que não funcione
- EDIT: Na verdade, funcionaria dentro de um trabalhador
O principal problema é que não se pode dizer se uma função é assíncrona até o tempo de execução. Isso resulta nas duas soluções que, pelo menos , funcionam muito mais lentas que o JS puro. Então eu pergunto:
Existem melhores maneiras de implementar?
Das respostas:
Aguardar palavra-chave (@ MI3Guy)
- Caminho C # (o que é bom !)
- introduz uma nova palavra-chave (que pode ser disfarçada como uma função (cidadão de primeira classe) que, por exemplo, lança se o programador tentar passar como argumento)
- Como saber se essa função é assíncrona? Nenhuma outra palavra-chave!
- Toda função que contém
await
palavras-chave se torna assíncrona? - Quando o código chega
await
, ele retorna uma promessa? Mais trata como uma função comum?
- Toda função que contém
synchronously (without blocking the thread, of course)
Você continua usando essa palavra. Eu não acho que significa o que você pensa que significa, porque de forma síncrona significa "bloquear o segmento".f(); a( b() );
se tornariaf().then(b).then(a);
Você não inclui funções.Respostas:
Supondo que o idioma seja digitado dinamicamente, posso ver duas maneiras diferentes de lidar com isso sem saber em tempo de compilação se uma chamada é assíncrona.
Uma abordagem seria assumir que cada chamada poderia ser assíncrona. No entanto, isso seria incrivelmente ineficiente e produziria muito código extra. Isso é essencialmente o que você está fazendo na opção 2.
Outra opção é usar fibras. As fibras são como threads leves que são agendadas pelo programa. A própria fibra decide quando abrir mão do controle. No entanto, estes requerem suporte do sistema operacional. Até onde eu sei, a única maneira de usar fibras no JavaScript é no node.js. Meu palpite é que, se você estiver compilando para JS, o objetivo (ou pelo menos um objetivo) é executar no navegador, o que exclui essa opção. Essa seria uma versão mais leve da opção 3.
Honestamente, eu consideraria adicionar uma palavra-chave como C #
await
. Isso tem várias vantagens, além de especificar que uma função é assíncrona. As funções podem ser chamadas para produzir futuros sem esperá-los imediatamente. Isso permite que várias operações assíncronas ocorram ao mesmo tempo, e não em sequência. Além disso, é melhor ser explícito sobre quais operações são assíncronas, porque outro código pode ser executado enquanto a operação está ocorrendo. Se o assíncrono for automático, pode ser surpreendente quando alguns dados que você está usando são modificados por um pedaço de código externo.Em C #, dentro de um método marcado como assíncrono, a instrução return é usada para retornar o valor que estará dentro do futuro, não o futuro em si. Observe, no entanto, que o modificador assíncrono não faz parte da assinatura do método.
await
pode ser usado com qualquer método que retorne um futuro.Se você não deseja adicionar um modificador assíncrono para funções, pode decidir (como afirmou) que qualquer função com uma espera seja tratada como se tivesse um modificador assíncrono implícito. Portanto, mesmo que uma função tenha um
await
, mas retorne antes de alcançá-lo, a função ainda deve retornar um futuro. Também recomendo expor alguma maneira de converter um valor em um futuro completo que contenha o valor, especialmente se não houver maneira de fazer isso implicitamente, adicionando o modificador assíncrono.fonte
if(){} else{}
, o programador também poderia definir uma estrutura semelhanteifZero(){} elseWhile(){}
). Mas parece que eu tenho que introduzir uma função de espera global que lança (ou pelo menos retorna indefinida) se o programador tentar atribuir seu valor a uma variável (as funções são cidadãos de primeira classe).