Async/Await non rispetta l'ordine! Errore mio o stranezza di JavaScript?

👤 Iniziato da @tullianobruno74
📅 26/06/2025 02:40
📁 Programmazione 🌐 IT
Avatar di tullianobruno74
Ciao a tutti! Sono incastrato su un problema che mi sta facendo impazzire. Sto lavorando a una funzione che processa dati da un'API, usando async/await in JavaScript. Teoricamente, le operazioni dovrebbero essere sequenziali, ma nella pratica l'ordine di esecuzione sembra saltare come un grillo! Ho preparato un esempio minimo:

```javascript
async function testOrder() {
console.log('Start');
await Promise.resolve();
console.log('Middle');
setTimeout(() => console.log('Timeout'), 0);
console.log('End');
}
testOrder();
```

Mi aspetterei 'Start', 'Middle', 'End', poi 'Timeout'... invece a volte 'End' appare prima di 'Timeout'. È un comportamento normale dell'event loop che non ho capito, o ho sbagliato io la logica? Qualcuno ha affrontato una cosa simile? Ogni suggerimento è benvenuto, grazie in anticipo!
Avatar di ranieroleone
Hai ragione a essere confuso, ma il comportamento è corretto. Quando usi `await Promise.resolve()`, il resto della funzione (`console.log('Middle')`, `'End'`) viene spostato in coda nella *microtask queue*, mentre `setTimeout` va nella *macrotask queue*. Dopo la prima parte sincrona (`'Start'`), JavaScript esegue i microtasks (quindi `'Middle'` e `'End'`) prima di processare il `setTimeout`. Tuttavia, se nell'esecuzione reale vedi `'End'` prima di `'Timeout'`, è normale: i microtasks vengono svolti prima delle macrotasks, ma il `setTimeout` con delay 0 non è garantito che si esegua immediatamente. Potrebbe esserci qualche altra asincronicità non visibile nel codice di esempio (magari incastrata in chiamate a API vere) che ti fa percepire salti di ordine. Controlla se hai promesse non awaitate o callback non sincronizzate. Il codice postato funziona come previsto, ma se devi forzare la coda del `setTimeout` a sincronizzarsi, devi incapsularlo in una promessa e awaitarla.
Avatar di finleyorlando59
Ecco, finalmente qualcuno che spiega le cose come stanno senza giri di parole! Ranieroleone ha detto tutto giusto, ma aggiungo un paio di osservazioni a modo mio.

Prima cosa: no, non è un bug, e no, non stai impazzendo (almeno non per questo motivo). JavaScript è così, e se non ti piace l’event loop, puoi solo imparare a conviverci. Quello che succede è che `setTimeout` va a finire nella macrotask queue, mentre il resto (dopo l’`await`) viene eseguito come microtask. E le microtask hanno *sempre* la precedenza sulle macrotask, punto.

Se per te l'ordine è fondamentale, una soluzione sporca ma efficace potrebbe essere:

```javascript
async function testOrder() {
console.log('Start');
await Promise.resolve();
console.log('Middle');
await new Promise(resolve => setTimeout(() => {
console.log('Timeout');
resolve();
}, 0));
console.log('End');
}
```

Così incapsuli il `setTimeout` in una promessa e lo aspetti, e sei sicuro che "End" arrivi dopo "Timeout". Però occhio: se lavori con codice che dipende da timing precisi, forse è meglio ripensare la logica anziché lottare contro l'event loop, perché prima o poi ti morde.

E comunque, sì, l'asincronia in JS è una schifezza che tutti odiamo all'inizio, poi ci si fa il callo.
Avatar di rufinocosta
@tullianobruno74, il comportamento è assolutamente normale e dimostra che hai frainteso come funziona l'event loop in JavaScript. Spiego meglio: quando chiami `setTimeout(() => console.log('Timeout'), 0)`, quel callback NON va in esecuzione subito dopo il log di 'Middle'! Viene buttato nella **macrotask queue**, mentre `console.log('End')` è un'operazione sincrona che resta nella call stack corrente.

Ecco perché vedi sempre:
1. 'Start' (sincrono)
2. 'Middle' (dopo await, ma ancora microtask)
3. 'End' (sincrono, prima che la macrotask queue venga processata)
4. Solo DOPO, quando la call stack è vuoto, parte 'Timeout'.

Ranieroleone ha ragione sulle code, ma Finleyorlando59 ti ha dato la soluzione pratica: se vuoi forzare l'ordine, devi **incapsulare setTimeout in una promessa** e mettere `await` davanti. Altrimenti, accetta che JavaScript non è veramente sequenziale con operazioni asincrone. Se l'ordine è critico, studiati il pattern delle code (micro/macro) o usa async hooks. Fossi in te, farei un test con `queueMicrotask()` per vedere la differenza live!
Avatar di ardentosi13
Okay, facciamo chiarezza. Il tuo codice funziona esattamente come previsto. Quando esegui `await Promise.resolve()`, il ciclo dell'event loop di JS manda il resto a 'coda microtask', mentre `setTimeout` va in 'coda macrotask'—due mondi separati. Il log 'Middle' e 'End' sono sincroni dopo l'await, quindi vengono stampati subito dopo 'Start', prima di processare qualsiasi macrotask. La 'Timeout' arriva sempre dopo, quando la call stack è vuota. Il problema non è nel tuo esempio, ma forse nel codice reale: se hai altre promesse non awaitate o operazioni asincrone non incapsulate, possono 'rubare' l'ordine. La soluzione di Finleyorlando59 è ok se proprio devi bloccare l'esecuzione per forza, ma in genere evita di abusare di setTimeout in coda sincrona—usa async/await per chiamate genuine a API o promesse, non per trucchi. Il vero consiglio? Studia come gira l'event loop, non lo eviti.
Avatar di tullianobruno74
Grazie mille @ardentosi13, la tua spiegazione sull'event loop è stata illuminante! Avevo sottovalutato la divisione tra microtask e macrotask. Ora ha senso: i log sincroni "Middle/End" finiscono nella coda microtask e scattano prima di ogni macrotask come il setTimeout.
Nel codice reale stavo mischiando operazioni async senza await e probabilmente è lì il problema. Hai ragione, meglio studiare il flusso dell'event loop invece di forzare soluzioni strane. Proverò a isolare meglio le promesse e farò un deep dive sui task queue.
La soluzione con setTimeout propesa da Finleyorlando59 la terrò come extrema ratio, ma seguirò il tuo consiglio di usare async/await solo per operazioni genuine. Grazie per aver chiarito il tutto!

La Tua Risposta

💬

Vuoi partecipare alla discussione?

Accedi o registrati per scrivere la tua risposta e unirti alla conversazione!