Executando verificação de segurança...
-7

Não, Javascript não é assincrono

Eu postei um comentário nesse post respondendo a afirmação que "PHP não é asíncrono, assim não tem Promise." com um exemplo de porque a afirmação de ter Promise não torna uma linguagem assincrona e fui trucidado nos downvotes, tanto que tive que apagar pra proder comentar, então logo de cara, não, ter Promisse não torna nada assincrono, Promisse é Promisse, assincrono é assincrono, por exemplo, um mecanismo simples de Promisse em PHP:

<?php

class Promise {
    public $status;
    public $resolved;
    public $rejected;
    public $result;
    private static $deadPromises = 0;
    public static $promisesList = [];
    private $generator;

    public function __construct($generatorFunction){
        $this->status = "pending";
        $this->generator = $generatorFunction();

        self::$promisesList[] = $this;
        $this->resume();
    }

    public function resume() {
        if ($this->status != "pending") return;

        try {
            // Check if the generator is valid and proceed
            if ($this->generator && $this->generator->valid()) {
                $this->generator->next();
            } else {
                $this->status = "resolved";
                self::$deadPromises++;
            }
        } catch (Exception $e) {
            self::$deadPromises++;
            $this->status = "rejected";
        }
    }

    static public function iterate() {
        while (true) {
            foreach (Promise::$promisesList as $promise) {
                $promise->resume();
            }
            if (Promise::$deadPromises == count(Promise::$promisesList)) {
                break;
            }
        }        
    }
}

function foreachGenerator($arr, $callback) {
    foreach ($arr as $key => $value) {
        $callback($key, $value);
        yield; // Yield here to pause after each callback execution
    }
}

// Main execution
echo "Inicio do loop principal<br>";


$p = new Promise(function () {
    return foreachGenerator([1, 2, 3, 4, 5, 6], function ($i, $v) {
        echo "$v<br>";
    });
});

echo "Fim do loop principal<br>";

// Loop das promisses
Promise::iterate();

?>

Nenhuma biblioteca extra, só PHP puro e a saída foi:

Inicio do loop principal
1
2
Fim do loop principal
3
4
5
6

Acabei de implementar um mecanismo de Promise em PHP, e PHP continua sincrono, qual o truque?

Javascript e sua forma de execução

Javascript executa as instruções linha a linha em uma única pilha de execução quando você faz:

console.log("Inicio");

p = (new Promise(() => { console.log("meio?");}));

console.log("Fim");

Javascript executa o primeiro console.log e vai para a próxima linha, só que nessa linha tem a declaração de uma Promise, uma Promise nada mais é que uma operação (e chamar uma função é uma operação) que pode ser concluída agora, depois ou nem ser concluida, é uma "promessa" como o nome sugere, então o que JS faz é executar essa função e se ela pausar sua execução por algum motivo, o interpretador segue para a próxima linha, que no caso é outro console.log, mas ele não só ignora ela, ele armazena essa Promise, depois que tudo que não for executado, o interpretador põe cada uma das promises que você declarou na pilha de execução e vai verificando o estado, enquanto estiver pending ela despausa a execução e segue para a próxima até que todas ou tenham como o estado seja fulfilled ou rejected, quando o estado mudar para um desses dois ela chama a função callback que você passou em thene catch respectivamente

Tirando a prova

Você pode ainda estar revoltado comigo achando que eu estou inventando que quero te enganar mas se liga só:

console.log("Inicio");

function* gerador1() {
    let n = -1;
    while (n < 10) {
        n+=2;
        yield n;
    }
}

function* gerador2() {
    let n = 0;
    while (n < 10) {
        n+=2;
        yield n;
    }
}

const p1 = new Promise((resolve) => {
    const generator = gerador1();
    const interval = setInterval(() => {
        const next = generator.next();
        if (next.done) {
            clearInterval(interval);
        } else {
            console.log(next.value);
        }
    }, 1);
});


const p2 = new Promise((resolve) => {
    const generator = gerador2();
    const interval = setInterval(() => {
        const next = generator.next();
        if (next.done) {
            clearInterval(interval);
        } else {
            console.log(next.value);
        }
    }, 1);
});
console.log("Fim");

Ao colar isso no console do navegador, você verá a saída:

Inicio
Fim
undefined
1
2
3
4
5
6
7
8
9
10
11

O undefined indica que a pilha de execução linha a linha seguiu conforme previsto, e a sequência de 1 a 11 demostra que p1 e p2 foram alternadas também conforme previsto, uma linguagem verdadeiramente assincrona possui mais de uma pilha de execução o que não é o caso de JS, e pilhas de execução não são threads diferente, então resumindo, assincronismo em JS é apenas uma ilusão de percepção, muito bem feita mas umas ilusão, um último exemplo caso por qualquer motivo que seja você ainda dúvide que JS é sincrono e assincronismo não é um truque:

function* gerador1() {
    let n = -1;
    while (n < 10) {
        n+=2;
        yield n;
    }
}

const p1 = new Promise((resolve) => {
    const generator = gerador1();
    const interval = setInterval(() => {
        const next = generator.next();
        if (next.done) {
            clearInterval(interval);
            resolve("Sequence complete");
        } else {
            console.log(next.value);
        }
    }, 500);
});

const p2 = new Promise((resolve) => {
    const generator = gerador1();
    const interval = setInterval(() => {
        const next = generator.next();
        if (next.done) {
            clearInterval(interval);
        } else {
            alert(`P1 não executa até o fim já que a pilha é a mesma :) e eu bloqueio a pilha, para P1 executar eu preciso liberar a pilha`);
        }
    }, 500);
});

Como agora da pra notar, o que Javascript tem são açúcares sintáticos setInterval nada mais faz que executar a função passada como argumento, bloqueando a pilha de execução, e bloqueando liberando a pilha pelo tempo especificado, ou seja, não é assincronismo, mas dá pra fazer assincronismo real em javascript, mas aí, assim como PHP dependem de extensões, não é mais o foco

Carregando publicação patrocinada...
4

Acho que a discussão precisa começar com a definição do que significa ser assíncrono. Me parece que todos os comentários abaixo estão falando sobre o mesmo assunto, mas discordam sobre o que significa ser assíncrono. A especificação do ECMAScript (o nome oficial do JavaScript) menciona suas capacidades para desenvolver funções assíncronas, bem como para interagir com elas. https://tc39.es/ecma262/ Então, talvez a discussão aqui deva ser sobre o que uma linguagem precisa ter para ser considerada assíncrona.

Gostaria de pedir que o autor, ou outros que defendem que ela não é ou não suporta funções assíncronas, apresentem:

  1. A definição do que significa ser assíncrono e
    1. Um exemplo de uma outra linguagem que é considerada assíncrona e que faz algo que não pode ser reproduzido em JavaScript com suas bibliotecas padrão.

Acho que isso ajudaria no entendimento.

Obrigado

2

Cristian, claramente o autor do post está confuso no conceito do que é ser assíncrono.
A definição foi colocada explicitamente ou implicitamente em outros comentários, não tem muito o que discutir.

O que eu acho que ele realmente está querendo dizer é sobre concorrência ou paralelismo na execução das threads.

No entanto, o Javascript (que quer dizer mais sobre sintaxe aqui) nem deveria entrar em discussão, pois seguindo a especificação do ECMAScript que você mesmo compartilhou, há os "motores" responsáveis por implementar isso e aí temos uma gama completa de implementações para usos diferentes.

O que eu tentei argumentar com o autor é que ele está discutindo algo que conceitualmente estava errado desde o início, todo o resto é consequência disso.

1

Um exemplo de uma outra linguagem que é considerada assíncrona e que faz algo que não pode ser reproduzido em JavaScript com suas bibliotecas padrão.

Goroutines, são assincronas de verdade já que são concorrentes, são single-threads e não podem ser reproduzidos em JavaScript com suas bibliotecas padrão apenas emuladas o que torna o desempenho incrívelmente lento

1

Eu programo em Go, e as goroutines não são executadas em uma única thread. Aqui está a documentação oficial do Go: https://go.dev/doc/effective_go. O segundo parágrafo da seção sobre goroutines:

Goroutines are multiplexed onto multiple OS threads so if one should block, such as while waiting for I/O, others continue to run. Their design hides many of the complexities of thread creation and management.

Esse trecho explica que as goroutines são distribuídas em múltiplas threads do sistema operacional.

1

Também programo em Go e a real é que depende, Go é inteligente, ele divide as gorrotinas se necessário apenas, você pode ter 100 gorrotinas se nessas 100 nenhuma bloquear a thread principal, go não cria novas thread, inclusive você pode testar com watch ps -o thcount <pid> dependendo de como o código é estruturado esse número não aumenta

1

Mano, tu tá insistindo em atribuir concorrência à assincronia. Isso está errado.

Você pode até achar que seja melhor que a função assíncrona funcione em paralelo, ou concorrentemente. Mas isso é só a sua opinião. Isso não faz parte do conceito de assíncronia e jamais fará. São conceitos e utilidades diferentes.

Pegue qualquer linguagem que você considere "realmente assíncrona" e coloque para rodar em um processador com um único núcleo de processamento (ou limite o acesso do programa a apenas um núcleo). Você já não consegue mais que o programa seja "realmente concorrente", porque o processador vai executar uma instrução por vez, uma atrás da outra, mesmo que a linguagem simule uma concorrência. Mas ainda assim você pode ter a assincronia, independentemente de quantos núcleos o programa tenha acesso. Porque a assincronia só depende da instrução ser executada (ou resolver) fora da ordem que foi deflagrada.

Segundo a sua definição, um programa "realmente assíncrono" deixaria de sê-lo se ficar limitado a um único núcleo de processamento, mas isso não é verdade, pelo contrário, a única coisa que acontece é que o programa deixa de ser "realmente concorrente".

Veja bem esse exemplo:

let someCounter = 0;
const asyncFunction = async () => await new Promise(resolve => setTimeout( ()=>resolve(someCounter++), 500) );
const theValues =
{
    // Recebe 1
    async: asyncFunction().then( value => theValues.async = value ),
    
    // Recebe 0
    sync: someCounter++,
    
    // Mostra 2
    get counter () { return someCounter },
}
setTimeout( () => console.log(theValues), 500 );

A propriedade async é claramente definida antes da propriedade sync, porém esta última recebe 0 do contador enquanto a primeira recebe 1 do contador.

Por que isso acontece? Porque a propriedade async resolve seu valor somente 500ms depois da propriedade sync.

Por mais que o JavaScript tenha uma pilha de execução que ocorre em ordem, isso não impede a assincronia. Faz parte do design da linguagem para manter a ordem. Se você tem dois timers de 1ms definidos imediatamente um após o outro, porque diabos seria interessante que o timer definido por último execute antes do que foi definido primeiro? Na minha opinião, o JavaScript faz muito bem em manter as coisas em ordem.

1

Sim, elixir/Erlang, go, v todas possuem greenthreads roda em uma única cpu, porém roda assíncronamente.

Tenho duas fontes, uma com um exemplo dado por Fabio Akita em vídeo: The Elixir of Life por Fabio Akita - DevInSantos 2015

E a outra é uma pergunta feita no site o Elixir: How are Elixir processes lightweight?

Não é que sou hate de javascript não, tem seus pontos negativos, mas não é um problema pra mim, além de ter me feito sentido o que foi dito no post original eu lembrei desse vídeo acima onde explicava isso.

E agradeço por sua forma serena de alcamar os animos de todos.

2

Obrigado pela resposta! O vídeo me ajudou a compreender melhor. Acho que a confusão é em distinguir entre multithreading e assincronia.

No vídeo, se eu entendi direito, ele menciona que Elixir é multithread e, como você disse, usa green threads. Já o JavaScript não é multithread, mas isso não significa que não seja assíncrono.

No exemplo do vídeo, ele é injusto com o JavaScript, pois utiliza uma função sleep bloqueante, que nao é parte da linguagem,mas uma biblioteca que ele installou, enquanto em Elixir ele usa uma função não bloqueante (de acordo com ele mesmo). Se ele usasse uma função bloqueante em Elixir e forçasse a execução em uma única thread, teria o mesmo problema.

Se esse é o caso e o JavaScript executa código assíncrono, então, desde que a função seja não bloqueante como a do exemplo em Elixir, deveria ser possível reproduzir o mesmo comportamento e realizar 400 chamadas em 4 segundos, correto? Decidi tentar! :)

Abaixo está a minha reprodução do código do vídeo usando a mesma função bloqueante.

var express = require("express");
var router = express.Router();

router.get("/", function (req, res) {
  require("sleep").sleep(1);
  res.json({ message: "Hello World!" });
});

module.exports = router;

function startServer() {
  var app = express();
  app.use(router);
  app.listen(3002);
  console.log("Server started at http://localhost:3002");
}

startServer();

E aqui está o resultado

Javascript Async siege -r 1 -c 10 http://localhost:3002
** SIEGE 4.1.6
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     2.03 secs:      26 bytes ==> GET  /
HTTP/1.1 200     3.03 secs:      26 bytes ==> GET  /
HTTP/1.1 200     4.04 secs:      26 bytes ==> GET  /
HTTP/1.1 200     5.05 secs:      26 bytes ==> GET  /
HTTP/1.1 200     6.05 secs:      26 bytes ==> GET  /
HTTP/1.1 200     7.05 secs:      26 bytes ==> GET  /
HTTP/1.1 200     8.06 secs:      26 bytes ==> GET  /
HTTP/1.1 200     9.06 secs:      26 bytes ==> GET  /
HTTP/1.1 200    10.07 secs:      26 bytes ==> GET  /

Transactions:                     10 hits
Availability:                 100.00 %
Elapsed time:                  10.07 secs
Data transferred:               0.00 MB
Response time:                  5.55 secs
Transaction rate:               0.99 trans/sec
Throughput:                     0.00 MB/sec
Concurrency:                    5.51
Successful transactions:          10
Failed transactions:               0
Longest transaction:           10.07
Shortest transaction:           1.02

Usando uma função bloqueante, consegui reproduzir a mesma lentidão mostrada no vídeo.

Agora, veja o que acontece se eu usar uma função sleep que não bloqueia:

var express = require("express");
var router = express.Router();

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

router.get("/", function (req, res) {
  //   require("sleep").sleep(1);
  sleep(1000).then(() => {
    res.json({ message: "Hello World!" });
  });
});

module.exports = router;

function startServer() {
  var app = express();
  app.use(router);
  app.listen(3002);
  console.log("Server started at http://localhost:3002");
}

startServer();

Resultado:

(base) ➜  Javascript Async siege -r 1 -c 10 http://localhost:3002 
** SIEGE 4.1.6
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /

Transactions:                     10 hits
Availability:                 100.00 %
Elapsed time:                   1.02 secs
Data transferred:               0.00 MB
Response time:                  1.01 secs
Transaction rate:               9.80 trans/sec
Throughput:                     0.00 MB/sec
Concurrency:                    9.93
Successful transactions:          10
Failed transactions:               0
Longest transaction:            1.02
Shortest transaction:           1.01

Observe que cada requisição levou cerca de 1 segundo, e o tempo total foi 1.02 segundos.

Posso até reproduzir o teste mais pesado do vídeo, com 2 repetições e 200 usuários paralelos:

(base) ➜  Javascript Async siege -r 2 -c 200 http://localhost:3002
** SIEGE 4.1.6
** Preparing 200 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
....
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /

Transactions:                    400 hits
Availability:                 100.00 %
Elapsed time:                   2.08 secs
Data transferred:               0.01 MB
Response time:                  1.03 secs
Transaction rate:             192.31 trans/sec
Throughput:                     0.00 MB/sec
Concurrency:                  197.96
Successful transactions:         400
Failed transactions:               0
Longest transaction:            1.06
Shortest transaction:           1.00

Note como as requisições individuais continuam em torno de 1 segundo, e o total é 2.08 segundos, mais rápido que Elixir no vídeo.

Com relação ao primeiro ponto, o JavaScript roda código assíncrono e consegue responder a chamadas em paralelo, mesmo usando uma única thread. No entanto, é importante lembrar que, se o código for escrito de forma síncrona, ele não será executado de maneira assíncrona. Cabe ao desenvolvedor garantir que o código seja escrito para rodar assincronamente.

Quando o JavaScript encontra funções assíncronas por natureza, como operações de I/O, ele retorna a execução para o fluxo principal do código, permitindo que outras instruções continuem sem esperar. Porém, se o código nunca chegar a uma função assíncrona, ele será executado de forma totalmente síncrona. No meu código acima, essa função assíncrona é o setTimeout. Em algumas linguagem como Python, o desenvolvedor tem que fazer isto explicitamente.

JavaScript se destacou justamente por facilitar a execução de funções de I/O, como leitura de arquivos e chamadas de rede de maneira assíncrona, permitindo que o código continue avançando sem esperar pela resposta do servidor remoto.

O que JavaScript não faz é executar código em paralelo. Todo o código roda em uma única thread e em um event loop. Portanto, se houver algo pesado para processar, como uma grande quantidade de dados, o JavaScript ficará limitado pela capacidade dessa única thread.

Me fale se o que escrevi faz sentido. Estou curioso para saber se consegui entender seu ponto e se estamos alinhados na diferença entre assíncrono e paralelo.

1

Cristian, macho eu criei alguns exemplos pra te dá sobre greenthread e goroutines e testei iria trazer o exemplo dos resultados quando eu percebi que o meu conhecimento de relacionado a threads estava fraco, pois acabava que não batia quando realizava o teste era muita teoria, e teoria equivocada.

E reconheço a minha confusão, peço desculpas e obrigado por sua paciência em explicar.

3

E? Só prova que PHP é uma coisa, JavaScript é outra coisa. Cada linguagem tem sua aplicação, uma foi inventada para front, outra para back e ambas são gambiarrados o tempo todo para virar aplicação desktop, front, back e aplicativo. Moral da história: dá pra fazer merda com qualquer coisa, seja electron, node, php gtk, native php....

1
1

Com todo respeito, mas você só provou o contrário com seus exemplos.
Parece que você está confundindo conceitos, na minha humilde opinião, diferentes.
Como o próprio ChatGPT resumiu aqui

Em JavaScript, "assíncrono" refere-se a operações que são executadas de forma não bloqueante, permitindo que o código continue a ser executado enquanto se espera que uma operação termine. Isso é essencial para JavaScript, que é uma linguagem de execução única (single-threaded) e precisa gerenciar tarefas demoradas, como chamadas de rede, sem interromper a execução do restante do código.

Por um acaso você estaria confundindo com paralelismo?

1

Rapaz, não é possível!
O cara deu uma ótima explicação sobre o conceito, e muita gente que dúvido que leu tudo, apenas deu downvote.

A comunidade da ficando tóxica na moral mesmo!

Um rapaz abaixo, disso você não está confundindo com paralelismo, que tem haver bicho?
O cara falou todo tempo só não deixou explicito o nome concorrência.

Lembro que a um bom tempo atrás eu fiz um Schedule na linguagem Euphoria, que não tem um tipo de thread chamada cooperativa, onde não um agente superior que fica dizendo quando parar uma execução para dar pra outro executar como funciona na concorrência, então o que fiz foi criar um agent que lida com isso.

Quem quiser olhar: https://github.com/Ddiidev/AgentSchedule

Enfim, vocês precisam parar e ler atentamente.

Ótimo conteúdo xafiyom362