En me basant sur l'excellente réponse de trincot, j'ai écrit une fonction réutilisable qui accepte un handler à exécuter sur chaque élément d'un tableau. La fonction elle-même renvoie une promesse qui vous permet d'attendre la fin de la boucle et la fonction de gestion que vous passez peut également renvoyer une promesse.
loop(items, handler) : Promise
Il m'a fallu du temps pour y arriver, mais je pense que le code suivant sera utilisable dans de nombreuses situations de rupture de promesse.
Copier-coller du code prêt :
// SEE https://stackoverflow.com/a/46295049/286685
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
Utilisation
Pour l'utiliser, appelez-la avec le tableau à boucler comme premier argument et la fonction de gestion comme deuxième. Ne pas passer de paramètres pour les troisième, quatrième et cinquième arguments, ils sont utilisés en interne.
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const items = ['one', 'two', 'three']
loop(items, item => {
console.info(item)
})
.then(() => console.info('Done!'))
Cas d'utilisation avancée
Examinons la fonction de gestion, les boucles imbriquées et la gestion des erreurs.
handler(current, index, all)
Le gestionnaire reçoit 3 arguments. L'élément courant, l'index de l'élément courant et le tableau complet sur lequel on boucle. Si la fonction de gestion doit effectuer un travail asynchrone, elle peut renvoyer une promesse et la fonction de boucle attendra que la promesse soit résolue avant de lancer l'itération suivante. Vous pouvez imbriquer les invocations de boucle et tout fonctionne comme prévu.
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
return loop(test, (testCase) => {
console.info(testCase)
})
.then(testNext)
.catch(testFailed)
}))
.then(() => console.info('All tests done'))
Traitement des erreurs
Beaucoup d'exemples de promesse-looping que j'ai examinés s'effondrent lorsqu'une exception se produit. Obtenir que cette fonction fasse la bonne chose a été assez délicat, mais pour autant que je puisse dire, elle fonctionne maintenant. Assurez-vous d'ajouter un gestionnaire de capture à toutes les boucles internes et invoquez la fonction de rejet lorsqu'elle se produit. Par exemple :
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
loop(test, (testCase) => {
if (idx == 2) throw new Error()
console.info(testCase)
})
.then(testNext)
.catch(testFailed) // <--- DON'T FORGET!!
}))
.then(() => console.error('Oops, test should have failed'))
.catch(e => console.info('Succesfully caught error: ', e))
.then(() => console.info('All tests done'))
UPDATE : paquet NPM
Depuis que j'ai écrit cette réponse, j'ai transformé le code ci-dessus en un paquet NPM.
Installer
npm install --save for-async
Importation
var forAsync = require('for-async'); // Common JS, or
import forAsync from 'for-async';
Utilisation (asynchrone)
var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
return new Promise(function(resolve){
setTimeout(function(){
console.info(item, idx);
// Logs 3 lines: `some 0`, `cool 1`, `array 2`
resolve(); // <-- signals that this iteration is complete
}, 25); // delay 25 ms to make async
})
})
Consultez le fichier readme du paquet pour plus de détails.
0 votes
En rapport : Création d'une promesse (ES6) sans commencer à la résoudre