Vol. 8 · № 03 · Un atelier TypeScriptA TypeScript workshop

Combiner des promesses, ou le type dit comment ça échoue Combining promises, or the type says how it fails

Promise.all lance en parallèle et rend un tuple typé — mais rejette en bloc au premier échec. allSettled ne rejette jamais et rend une union discriminée par résultat (le Vol 3 revient). race et any rendent une union, réglée au premier. Deux pièges : le tuple de all se perd si le tableau n'est pas inline, et all sur des opérations indépendantes sacrifie le succès partiel. Promise.all runs in parallel and returns a typed tuple — but rejects wholesale on the first failure. allSettled never rejects and returns a discriminated union per result (Vol 3 returns). race and any return a union, settled on the first. Two pitfalls: all's tuple is lost if the array isn't inline, and all on independent operations sacrifices partial success.

AudienceAudience
Dev qui combine des promesses et néglige les types et le rejet des combinateurs Dev who combines promises and overlooks the combinators' types and reject behavior
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ Promise.allCombinateursTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Promise.all lance en parallèle — et rend un tuple typé, position par position.Promise.all runs in parallel — and returns a typed tuple, position by position.

On a souvent plusieurs opérations asynchrones indépendantes. Les enchaîner par des await successifs les sérialise sans raison : la seconde attend la première. Les combinateurs lancent tout de front — et leurs types réservent des subtilités qu'on néglige. Promise.all est le plus utile : il attend que tout résolve, et rend un tuple où chaque position garde son type exact. Pas un tableau de l'union des types : un tuple [User, Post[]], déstructurable proprement.You often have several independent async operations. Chaining them with successive awaits serializes them for no reason: the second waits for the first. Combinators launch everything at once — and their types hold subtleties that get overlooked. Promise.all is the most useful: it waits for all to resolve, and returns a tuple where each position keeps its exact type. Not an array of the union of types: a tuple [User, Post[]], cleanly destructurable.

Des await successifs — sérialisés sans raisonSuccessive awaits — serialized for no reason
const user = await fetchUser();    // attend…
const posts = await fetchPosts();  // …puis attend (alors qu'ils sont indépendants)
// sérialisé sans raison : posts n'avait pas besoin d'attendre user
Comment combiner, et avec quels types ?How to combine, and with which types?
await … awaitawait … await
en sérieserial
lent : chacun attend le précédentslow: each waits for the prior
Promise.all([…])
en parallèlein parallel
tuple typé : [User, Post[]]typed tuple: [User, Post[]]

Promise.all apporte deux choses : le parallélisme à l'exécution, et un tuple au niveau type — [A, B], pas (A | B)[]. Chaque résultat ressort avec son type d'origine, prêt à être déstructuré.Promise.all brings two things: parallelism at runtime, and a tuple at the type level — [A, B], not (A | B)[]. Each result comes back with its original type, ready to destructure.

Promise.all — parallèle, et chaque type préservéPromise.all — parallel, and each type preserved
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
//      ^^^^  ^^^^^ chacun son TYPE : user : User, posts : Post[]
// lancés en parallèle, et Promise.all rend un TUPLE typé position par position
Le réflexe du numéroThe issue's reflex

« Quel combinateur, et que dit son type ? » all rend un tuple mais rejette en bloc ; allSettled ne rejette jamais mais rend des unions ; race rend une union. Choisir le combinateur, c'est choisir un comportement et une forme de type."Which combinator, and what does its type say?" all returns a tuple but rejects wholesale; allSettled never rejects but returns unions; race returns a union. Choosing the combinator is choosing a behavior and a type shape.

Le numéro qui clôt l'asynchrone.The issue that closes async.

Après le succès seul (8.1) et l'erreur non typée (8.2), voici la composition. Les combinateurs ont des types soignés — un tuple pour all, une union discriminée pour allSettled — mais aussi des pièges d'inférence (tableau vs tuple) et des comportements de rejet qu'il faut connaître pour choisir juste.After success-only (8.1) and the untyped error (8.2), here's composition. The combinators have careful types — a tuple for all, a discriminated union for allSettled — but also inference pitfalls (array vs tuple) and reject behaviors you must know to choose right.

№ 02La surpriseThe surprise4 min

all est tout-ou-rien ; allSettled rend une union discriminée par résultat.all is all-or-nothing; allSettled returns a discriminated union per result.

La surprise qui mord : Promise.all rejette dès la première promesse qui échoue, et jette tous les autres résultats — même ceux qui avaient résolu. C'est voulu (tout ou rien), mais on l'oublie quand les opérations sont indépendantes. allSettled est l'autre régime : il attend que tout se règle, ne rejette jamais, et rend pour chaque élément une union discriminée — fulfilled avec sa value, ou rejected avec sa reason. C'est le Vol 3 qui revient : on narrowe sur status pour traiter chaque sort séparément.The surprise that bites: Promise.all rejects as soon as the first promise fails, and discards all other results — even those that had resolved. It's intentional (all-or-nothing), but forgotten when the operations are independent. allSettled is the other regime: it waits for everything to settle, never rejects, and returns for each element a discriminated union — fulfilled with its value, or rejected with its reason. It's Vol 3 returning: you narrow on status to handle each fate separately.

Un seul échec, et tous les résultats sont perdusOne failure, and all results are lost
const [a, b, c] = await Promise.all([fa(), fb(), fc()]);
// ✗ si fb() rejette : Promise.all rejette AUSSITÔT, a et c sont JETÉS
//   on n'obtient ni les succès, ni un détail par opération — tout ou rien

Promise.all court-circuite au premier rejet : le tuple n'est jamais formé, et les résultats des opérations qui avaient réussi sont silencieusement perdus — les opérations elles-mêmes continuent de tourner : rien n'est annulé. Parfait quand on a besoin de tout ; dangereux quand un échec partiel devrait être toléré.Promise.all short-circuits on the first reject: the tuple is never formed, and the results of operations that had succeeded are silently lost — the operations themselves keep running: nothing is cancelled. Perfect when you need everything; dangerous when partial failure should be tolerated.

all demande « tout a-t-il réussi ? » et jette le reste sinon. allSettled demande « qu'est-il arrivé à chacun ? » — et te le rend, typé, un par un.all asks "did everything succeed?" and discards the rest otherwise. allSettled asks "what happened to each?" — and hands it back, typed, one by one.
№ 03La mécaniqueThe mechanics5 min

Quatre combinateurs, quatre types — et l'inférence tuple qui tient à un détail.Four combinators, four types — and the tuple inference that hangs on a detail.

Les quatre combinateurs ont des comportements et des types distincts. all rend un tuple et rejette si l'un rejette. allSettled rend un tuple d'unions discriminées et ne rejette jamais. race rend l'union des types et se règle au premier settled, succès ou échec. any se règle au premier succès, et rejette avec une AggregateError si tous échouent. Le point d'inférence à connaître : all ne préserve le tuple que si le tableau est passé inline ; via une variable de type Promise<X>[], on retombe sur un simple tableau, et les positions sont perdues.The four combinators have distinct behaviors and types. all returns a tuple and rejects if one rejects. allSettled returns a tuple of discriminated unions and never rejects. race returns the union of types and settles on the first settled, success or failure. any settles on the first success, and rejects with an AggregateError if all fail. The inference point to know: all preserves the tuple only if the array is passed inline; through a variable of type Promise<X>[], you fall back to a plain array, and positions are lost.

Les quatre — comportement et typeThe four — behavior and type
Promise.all([fa(), fb()])        // [A, B]          rejette si UN rejette
Promise.allSettled([fa(), fb()]) // [Settled<A>, Settled<B>]  ne rejette jamais
Promise.race([fa(), fb()])       // A | B           le PREMIER réglé (succès OU échec)
Promise.any([fa(), fb()])        // A | B           le premier RÉUSSI (sinon AggregateError)
Le piège d'inférence — tuple inline vs tableau-variableThe inference pitfall — inline tuple vs variable-array
// Inline : TypeScript infère un TUPLE — positions conservées
const r1 = await Promise.all([fa(), fb()]);   // [A, B]  ✓
r1[0];   // A

// Via une variable Promise<X>[] : il infère un TABLEAU — positions perdues
const arr = [fa(), fb()];                      // (Promise<A> | Promise<B>)[]
const r2 = await Promise.all(arr);             // (A | B)[]  ✗ plus de tuple
Quel combinateur, pour quoiWhich combinator, for what
Tout / chacunAll / each
alltuple ; rejette en bloctuple; rejects wholesale
allSettledunions ; ne rejette jamaisunions; never rejects
Le premierThe first
raceunion ; 1er réglé (succès/échec)union; 1st settled (success/fail)
anyunion ; 1er réussi (sinon Aggregate)union; 1st success (else Aggregate)
Les voisins, seulement nommés.The neighbors, only named.

Pour forcer le tuple depuis une variable, on annote ([Promise<A>, Promise<B>]) ou l'on garde l'appel inline. as const sur un tableau de valeurs (Vol 4.3) fige les positions en littéraux — utile pour un tuple hétérogène. Awaited<T> (8.1) intervient sous le capot pour déballer chaque élément. Et any ne doit pas se confondre avec le type any du Vol 4 — ici, c'est Promise.any.To force the tuple from a variable, annotate ([Promise<A>, Promise<B>]) or keep the call inline. as const on a value array (Vol 4.3) freezes positions into literals — handy for a heterogeneous tuple. Awaited<T> (8.1) works under the hood to unwrap each element. And any mustn't be confused with Vol 4's any type — here, it's Promise.any.

№ 04Le piègeThe trap4 min

Promise.all pour des opérations indépendantes : un échec coule tout le reste.Promise.all for independent operations: one failure sinks the rest.

Le contre-exemple : prendre Promise.all par défaut, même quand les opérations sont indépendantes et qu'un échec partiel devrait être toléré. Trois envois d'emails, une notification qui tombe — et tout le all rejette : on ne sait plus qui est parti, on ne peut pas retenter sélectivement, et le résultat d'un succès se retrouve effacé par l'échec d'un voisin — l'envoi, lui, a bien eu lieu. all est fait pour « j'ai besoin de tout, sinon rien ». Pour « je veux le sort de chacun », c'est allSettled. Le bon combinateur est une décision, pas un réflexe.The counter-example: reaching for Promise.all by default, even when the operations are independent and partial failure should be tolerated. Three email sends, one notification drops — and the whole all rejects: you no longer know which went out, you can't retry selectively, and a success's result gets wiped by a neighbor's failure — the send itself did happen. all is for 'I need all of it, or nothing'. For 'I want each one's fate', it's allSettled. The right combinator is a decision, not a reflex.

Un échec efface des succès qui auraient dû compterOne failure wipes out successes that should have counted
// 3 envois d'emails indépendants — un échec ne devrait pas faire perdre les autres :
await Promise.all([
  sendEmail(a), sendEmail(b), sendEmail(c),
]);
// ✗ si l'envoi à b rejette, Promise.all rejette : a et c partent quand même (rien
//   n'est annulé), mais on n'a AUCUN détail de qui a réussi. Tout ou rien, là où il fallait du partiel.

Le rejet en bloc efface l'information : impossible de savoir qui a réussi, ni de retenter les seuls ratés. Promise.all n'est pas « lancer plusieurs trucs » — c'est « j'ai besoin de la totalité ». Sur de l'indépendant, c'est le mauvais contrat.The wholesale reject erases information: you can't tell who succeeded, nor retry only the failures. Promise.all isn't "launch several things" — it's "I need the whole lot". On independent ops, it's the wrong contract.

Le petit piège d'inférence, en prime.The small inference pitfall, as a bonus.

Et l'écueil de type du chapitre 3 reste tapi : passer à Promise.all une variable de type Promise<X>[] au lieu d'un tableau inline élargit le résultat de tuple à tableau ((A | B)[]), et la déstructuration perd les types. Garde l'appel inline, ou annote le tuple. Deux décisions distinctes à chaque combinaison : quel comportement de rejet, et quelle forme de type — ne laisse ni l'un ni l'autre au hasard.And chapter 3's type snag stays lurking: passing Promise.all a variable of type Promise<X>[] instead of an inline array widens the result from tuple to array ((A | B)[]), and destructuring loses the types. Keep the call inline, or annotate the tuple. Two distinct decisions at every combination: which reject behavior, and which type shape — leave neither to chance.

Choisir all ou allSettled, ce n'est pas une question de style — c'est décider si l'échec d'un seul doit emporter tous les autres.Choosing all or allSettled isn't a matter of style — it's deciding whether one's failure should take down all the rest.
№ 05Bilan & éditoWrap-up & editorial2 min

Combiner, c'est choisir un comportement de rejet et une forme de type.Combining is choosing a reject behavior and a type shape.

Les combinateurs lancent plusieurs promesses de front, et chacun a son type. all rend un tuple et rejette en bloc ; allSettled rend des unions discriminées et ne rejette jamais ; race et any rendent une union, réglée au premier. Deux pièges : le tuple de all se perd si le tableau n'est pas inline, et all sur des opérations indépendantes sacrifie le succès partiel. Choisir le combinateur, c'est une décision — pas un réflexe.Combinators launch several promises at once, and each has its type. all returns a tuple and rejects wholesale; allSettled returns discriminated unions and never rejects; race and any return a union, settled on the first. Two pitfalls: all's tuple is lost if the array isn't inline, and all on independent operations sacrifices partial success. Choosing the combinator is a decision — not a reflex.

Tu veux…You want to…Le combinateurThe combinatorCe que ça coûteWhat it costs
Tout, ou rienAll, or nothingPromise.all → tuple [A, B].Promise.all → tuple [A, B].Rejette au premier échec — les autres sont jetés.Rejects on the first failure — the rest discarded.
Le sort de chacunEach one's fatePromise.allSettled → unions discriminées.Promise.allSettled → discriminated unions.Narrowing sur status (Vol 3) ; ne rejette jamais.Narrow on status (Vol 3); never rejects.
Le premier réglé / réussiThe first settled / succeededrace / any → union A | B.race / any → union A | B.race se règle aussi sur un échec ; any lève Aggregate.race settles on a failure too; any throws Aggregate.
Ne PAS l'employerNOT doall sur de l'indépendant / via une variable non-inline.all on independent ops / via a non-inline variable.Succès partiel perdu ; tuple dégradé en tableau.Partial success lost; tuple degraded to array.
Le combinateur n'est pas qu'une commodité d'exécution — son type dit ce que tu obtiens, et son rejet, ce que tu perds.The combinator isn't just a runtime convenience — its type says what you get, and its reject, what you lose.
Mot de l'éditeurFrom the editor

« Le type dit aussi comment ça échoue. »"The type also says how it fails."

Les combinateurs paraissent interchangeables — on lance plusieurs promesses, on attend le résultat. Mais leurs types racontent des histoires différentes, et c'est tout l'intérêt de les lire. Promise.all forme un tuple, position par position : on déstructure proprement, chaque résultat avec son type — à condition de passer le tableau inline, sans quoi l'inférence retombe sur un tableau et les positions s'effacent. allSettled, lui, ne rejette jamais et rend une union discriminée par élément : le Vol 3 ressurgit, et l'on narrowe sur status. Le type n'est pas qu'un décor : il encode le comportement de rejet.Combinators look interchangeable — you launch several promises, you await the result. But their types tell different stories, and reading them is the whole point. Promise.all forms a tuple, position by position: you destructure cleanly, each result with its type — provided you pass the array inline, else inference falls back to an array and the positions vanish. allSettled, for its part, never rejects and returns a discriminated union per element: Vol 3 resurfaces, and you narrow on status. The type isn't mere decoration: it encodes the reject behavior.

Ainsi se clôt le Vol 8 : le type d'une promesse décrit le succès (8.1), tait l'erreur (8.2), et — pour les combinateurs — encode jusqu'à la façon dont l'ensemble peut échouer (8.3). Reste un dernier volume, qui prend de la hauteur sur tout le reste : le réglage du compilateur. Jusqu'où le régler pour qu'il prouve, sans payer un prix qu'on ne comprend pas ? On y aborde strict et ses drapeaux, le coût de compilation des types trop malins, et la limite ultime — les déclarations externes qui peuvent mentir. Le compilateur n'est pas un oracle : il n'est jamais plus fiable que ce qu'on lui a déclaré.So Vol 8 closes: a promise's type describes success (8.1), withholds the error (8.2), and — for combinators — even encodes how the whole can fail (8.3). One last volume remains, rising above all the rest: the compiler's settings. How far to tune it so it proves, without paying a price you don't understand? We tackle strict and its flags, the compile cost of too-clever types, and the ultimate limit — external declarations that can lie. The compiler isn't an oracle: it's never more reliable than what you declared to it.