Linguagem compilada para JS - a maneira mais elegante de fazer esperas no estilo síncrono

8

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:

  1. atuação
  2. Compatibilidade com versões anteriores
  3. Legibilidade do código compilado (não é realmente importante)

Existem várias maneiras possíveis de implementá-lo, essas três me vieram à mente:

  1. Usando promessas:

    • f(); a( b() ); se transformaria em
    • f().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
  2. Usando geradores :

    • f(); a( b() ); se transformaria em
    • yield 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 )
  3. 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 awaitpalavras-chave se torna assíncrona?
    • Quando o código chega await, ele retorna uma promessa? Mais trata como uma função comum?
m93a
fonte
11
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".
Mason Wheeler
2
Leia mais sobre continuações e estilo de passagem de continuação
Basile Starynkevitch 08/04
1
@MasonWheeler: Presumo que o op significa que ele deseja usar a sintaxe no estilo síncrono.
Brian
Não chamarei isso de resposta, mas o que você está tentando parece um pouco com o que o C # faz com suas funções assíncronas. Eu sinto que o método mais consistente com valores de retorno seria o uso de promessas, e sim, isso envolveria muitas funções anônimas. Você pode tentar encontrar artigos sobre como o código C # é compilado para referência; provavelmente é tão ineficiente em alguns aspectos.
precisa saber é o seguinte
2
f(); a( b() );se tornaria f().then(b).then(a);Você não inclui funções.
precisa saber é o seguinte

Respostas:

3

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. awaitpode 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.

MI3Guy
fonte
Meu objetivo original era criar uma linguagem totalmente consistente sem estruturas que não pudessem ser reutilizadas (por exemplo, se houver if(){} else{}, o programador também poderia definir uma estrutura semelhante ifZero(){} 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).
M9a
Atualizada a pergunta (na parte inferior). Você poderia me dizer o que acha das perguntas? Obrigado.
M93a 9/04/2015
Eu adicionei mais detalhes com base nas suas atualizações. Deixe-me saber se você quis dizer algo diferente.
usar o seguinte comando