JS protip : faire une pause avec setTimeout(), oui, mais en mode await !
Par Christophe Porteneuve • Publié le 8 février 2023 • 3 min

This page is also available in English.

On est en 2023, et les promesses comme async / await sont désormais bien ancrées (même si je vois toujours plein de gens faire n’importe quoi).

Et pourtant, on utilise toujours ces bons vieux setTimeout() et setInterval()… Basés callbacks. Un reliquat du passé, bien sûr, alors on n’a pas le choix… Ou alors si ?!

Tout vient à point à qui sait attendre

Commençons par le côté back. Node.js propose depuis sa toute première version les API setTimeout() et setInterval() issues du navigateur ; sans ça (et console), personne ne serait venu ! 😅

Ces API sont bien entendues basés callbacks, mais depuis Node 15 une version basée promesses est également disponible :

import { setTimeout } from 'node:timers/promises'

console.log(new Date().toLocaleTimeString('fr')) // => 15:07:12
await setTimeout(1000)
console.log(new Date().toLocaleTimeString('fr')) // => 15:07:13

La classe ! 🤩

Perso, quand je l’utilise comme ça, j’ai même tendance à la renommer :

import { setTimeout as sleep } from 'node:timers/promises'

await sleep(1000)

Évidemment, contrairement au bon vieux sleep des univers bloquants, ça ne bloque pas le thread : ça reste des promesses, après tout ! C’est suspensif, pas bloquant.

Pas que setTimeout()

Tout ce qui vient de timers propose la même chose, notamment setInterval() et setImmediate().

Pour les intervalles, tu te demandes peut-être comment ça se passe, vu que c’est récurrent ? Eh bien ça nous renvoie un itérable asynchrone (qui renvoie toujours la même valeur, que tu peux d’ailleurs choisir de fournir en 2e argument), consommable par exemple avec une boucle for…await :

import { setInterval } from 'node:timers/promises'

let count = 5
console.log(new Date().toLocaleTimeString('fr')) // => 15:07:12

for await (const _ of setInterval(1000)) {
if (count-- === 0) break

console.log(new Date().toLocaleTimeString('fr'))
// => 15:07:13, 15:07:14, 15:07:15, 15:07:16, 15:07:17
}

console.log(new Date().toLocaleTimeString('fr')) // => 15:07:18

En pratique, le clearInterval() revient simplement à quitter la boucle (comme ici avec break), mais si tu veux pouvoir annuler l’intervalle depuis un autre point du code, il y a le même mécanisme d’annulation découplée que pour tout le reste des travaux à base de promesses : AbortController et AbortSignal.

import { setInterval } from 'node:timers/promises'

const ac = new AbortController()

for await (const _ of setInterval(1000, { signal: ac.signal })) {
// …
}

// Du code ailleurs pourrait avoir une référence au contrôleur et
// juste faire quand bon lui semble :
ac.abort()

On trouve le même type de variations pour les écouteurs d’événements (ex. await once(stream, 'close') avec node:events), l’accès au système de fichiers (ex. await readdir(path) avec node:fs/promises), ou même les flux en lecture (flux Node classiques ou ReadableStream compatibles Web), qui sont aussi des itérables asynchrones sujets aux signaux.

Bref, await c’est la vie ! 😎

C’est dispo où ?

Pour les timers, depuis Node 15. Pour le système de fichiers, les flux en lecture et les événements, depuis Node 10 (mais il aura fallu attendre Node 14 pour le chemin de module noyau fs/promises, et Node 15 pour les signaux).

Et côté navigateur ?

Seules les nouvelles API sont basées promesses, mais tu peux très facilement enrober setTimeout :

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

Si tu veux vraiment aller au bout et gérer les signaux, c’est jouable (c’est dispo sur tous les navigateurs modernes depuis des lustres) :

function sleep(delay, { signal } = {}) {
return new Promise((resolve, reject) => {
const timer = setTimeout(resolve, delay)

signal?.addEventListener(
'abort',
() => {
clearTimeout(timer)
reject(signal.reason)
},
{ once: true }
)
})
}

(Et pour setInterval, ce n’est pas très difficile, je te laisse essayer 😉).

Des astuces en veux-tu en voilà !

On a tout plein d’articles et de vidéos existants et encore beaucoup à venir. Pour ne rien manquer, tu devrais penser à t’abonner à notre newsletter, à notre chaîne YouTube, nous suivre sur Twitter ou encore mieux, à suivre une de nos formations du feu de dieu 🔥 !