JavaScript || Promise – Resolve Promises In Order Of Completion Using Vanilla JavaScript
The return values of Promise.all are returned in the order that the Promises were passed in, regardless of completion order. Sometimes, it is nice to know which promise completed first.
The following is sample code which demonstrates how to resolve an array of promises and return the results in order of completion.
Contents
1. Resolve By Completion
2. Fail-Fast Behavior
3. Timeout
4. Utils.resolveByCompletion
5. More Examples
The examples below demonstrate the return values of Promise.all compared to ‘resolve by completion‘.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Promise all example <script> (() => { let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve) => setTimeout(() => resolve('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // Promise all Promise.all(promises).then(values => { console.log(values); }).catch(error => { console.log(error); }); })() </script> // expected output: /* [ "A (slow)","B (slower)","C (fast)" ] */ |
‘Resolve by completion’ returns the completed values from fastest to slowest.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Resolve By Completion <script> (() => { let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve) => setTimeout(() => resolve('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // Promise values are returned in the order of completion Utils.resolveByCompletion(promises).then(values => { console.log(values); }).catch(error => { console.log(error); }); })() </script> // expected output: /* [ "C (fast)","A (slow)","B (slower)" ] */ |
2. Fail-Fast Behavior
Promise.all is rejected if any of the elements are rejected. For example, if you pass in multiple promises that will resolve, and one promise that rejects, then Promise.all will reject immediately.
Similar to Promise.allSettled, ‘Resolve by completion’ allows returning the promises rejected value to the result list.
The following example demonstrates this behavior.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Promise all example <script> (() => { let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve, reject) => setTimeout(() => reject('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // Rejected value is returned to the 'catch' function Promise.all(promises).then(values => { console.log(values); }).catch(error => { console.log('Rejected: ', error); }); })() </script> // expected output: /* Rejected: B (slower) */ |
‘Resolve by completion’ returns the rejected value as apart of the result list, depending on the boolean ‘rejectOnError’ parameter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Resolve By Completion <script> (() => { let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve, reject) => setTimeout(() => reject('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // Rejected promise values are returned when the parameter 'rejectOnError' is set to false. // Setting the parameter to true will reject the operation like normal. Utils.resolveByCompletion(promises, false).then(values => { console.log(values); }).catch(error => { console.log('Rejected: ', error); }); })() </script> // expected output: /* [ "C (fast)","A (slow)","B (slower)" ] */ |
3. Timeout
By default, ‘resolve by completion’ has no timeout. If setting a timeout is desired, it can be done like so.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Resolve By Completion - Timeout <script> (() => { let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve) => setTimeout(() => resolve('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // The operation will reject when timeout period expires. // The operation will reject on timeout, regardless of the 'rejectOnError' parameter Utils.resolveByCompletion(promises, false, 500).then(values => { console.log(values); }).catch(error => { console.log(error); }); })() </script> // expected output: /* Error: Timeout of 500ms expired */ |
4. Utils.resolveByCompletion
The following is Utils.resolveByCompletion. Include this in your project to start using!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
// ============================================================================ // Author: Kenneth Perkins // Date: Jul 8, 2020 // Taken From: http://programmingnotes.org/ // File: Utils.js // Description: Javascript that handles general utility functions // ============================================================================ /** * NAMESPACE: Utils * USE: Handles general utility functions. */ var Utils = Utils || {}; (function(namespace) { 'use strict'; // -- Public data -- // Property to hold public variables and functions let exposed = namespace; /** * FUNCTION: resolveByCompletion * USE: Returns a promise that will resolve when all of the input's * promises have resolved, in order of completion. * @param promises: An array of promises. * @param rejectOnError: Optional. Boolean that indicates if the operation * should reject if any of the input promises reject or throw an error. * If set to true, the operation is rejected if any of the input * promises reject or throw an error. * If set to false, the promises rejected value is added to the result list. * @param timeout: Optional. Integer that indicates how long to wait * (in milliseconds) for the promise group to complete. * @return: A promise that will contain the input promises results on completion. */ exposed.resolveByCompletion = (promises, rejectOnError = true, timeout = null) => { return new Promise(async (resolve, reject) => { try { let results = [] let promiseMap = new Map(); Array.prototype.forEach.call(promises, (promise, index) => { let promiseResult = { index: index, value: null, error: null }; let mapValue = null; if (promise instanceof Promise) { mapValue = promise .then(value => {promiseResult.value = value; return promiseResult}) .catch(error => {promiseResult.error = error; return promiseResult}) } else { mapValue = promiseResult; promiseResult.value = promise; } promiseMap.set(index, mapValue); }); let start = Date.now(); let isTimedOut = () => { let result = false; if (timeout) { let elapsed = (Date.now() - start); result = elapsed >= timeout; } return result; } while (promiseMap.size > 0) { let promiseResult = await Promise.race(promiseMap.values()); if (!promiseMap.delete(promiseResult.index)) { throw new Error('Error occurred processing values'); } if (promiseResult.error) { if (rejectOnError) { reject(promiseResult.error); return; } results.push(promiseResult.error); } else { results.push(promiseResult.value); } if (isTimedOut()) { throw new Error(`Timeout of ${timeout}ms expired`); } } resolve(results); } catch (e) { reject(e); } }); } // -- Private data -- (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(Utils)); // http://programmingnotes.org/ |
5. More Examples
Below are more examples demonstrating the use of ‘Utils.resolveByCompletion’. Don’t forget to include the module when running the examples!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Jul 8, 2020 // Taken From: http://programmingnotes.org/ // File: utils.html // Description: Demonstrates the use of functions in Utils.js // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Utils.resolveByCompletion Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } .output { text-align: left; } pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; } .string { color: green; } .number { color: darkorange; } .boolean { color: blue; } .null { color: magenta; } .key { color: red; } </style> <!-- // Include module --> <script type="text/javascript" src="./Utils.js"></script> </head> <body> <div class="main"> My Programming Notes Utils.resolveByCompletion Demo <pre><code><div class="output"></div></code></pre> </div> <script> (() => { return; let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve) => setTimeout(() => resolve('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // Promise values are returned in the order of completion Utils.resolveByCompletion(promises).then(values => { console.log(values); print('Promise values are returned in the order of completion', values); }).catch(error => { console.log(error); }); })() </script> <script> (() => { return; let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve, reject) => setTimeout(() => reject('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // Rejected promise values are returned when the parameter 'rejectOnError' is set to false Utils.resolveByCompletion(promises, false).then(values => { console.log(values); print('Rejected promise values are returned when the parameter "rejectOnError" is set to false', values); }).catch(error => { console.log('Rejected: ', error); }); })() </script> <script> (() => { return; let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve) => setTimeout(() => resolve('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; // The operation will reject when timeout period expires Utils.resolveByCompletion(promises, false, 500).then(values => { console.log(values); print('The operation will reject when timeout period expires', values); }).catch(error => { console.log(error); print('', error.message); }); })() </script> <script> (async () => { //return; // Using promise all let promiseAllResult1 = await Promise.all(example1()); print('1. Resolved promises', promiseAllResult1, 4); // Using resolve by completion let resolveByResult1 = await Utils.resolveByCompletion(example1()); print('1. Resolved promises ordered by completion', resolveByResult1, 4); // Using promise all let promiseAllResult2 = await Promise.all(example2()); print('2. Resolved promises', promiseAllResult2, 4); // Using resolve by completion let resolveByResult2 = await Utils.resolveByCompletion(example2()); print('2. Resolved promises ordered by completion', resolveByResult2, 4); // Using promise all let promiseAllResult3 = await Promise.all(example3()); print('3. Resolved promises', promiseAllResult3, 4); // Using resolve by completion let resolveByResult3 = await Utils.resolveByCompletion(example3()); print('3. Resolved promises ordered by completion', resolveByResult3, 4); // Using promise all let promiseAllResult4 = await Promise.all(example4()); print('4. Resolved promises', promiseAllResult4, 4); // Using resolve by completion let resolveByResult4 = await Utils.resolveByCompletion(example4()); print('4. Resolved promises ordered by completion', resolveByResult4, 4); // Using promise all let promiseAllResult5 = Promise.all(example5()).catch((error) => { print('5. Promise.all rejected item', error, 4); }); // Using resolve by completion let resolveByResult5 = await Utils.resolveByCompletion(example5(), false); print('5. resolveByCompletion including rejected item', resolveByResult5, 4); })() function example1() { let promises = [ new Promise((resolve) => setTimeout(() => resolve('A (slow)'), 1000)), new Promise((resolve) => setTimeout(() => resolve('B (slower)'), 2000)), new Promise((resolve) => setTimeout(() => resolve('C (fast)'), 10)) ]; return promises; } function example2() { let promises = [ new Promise(resolve => {setTimeout(resolve, 200, 'slow');}), 'instant', new Promise(resolve => {setTimeout(resolve, 50, 'quick');}) ]; return promises; } function example3() { let promises = [ Promise.resolve(3), 42, new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo');}) ]; return promises; } function example4() { let promises = [1,2,3, Promise.resolve(444)]; return promises; } function example5() { let promises = [1,2,3, Promise.reject(555)]; return promises; } </script> <script> function print(desc, obj, indent = 0) { let text = (desc.length > 0 ? '<br />' : '') + desc + (desc.length > 0 ? '<br />' : ''); let objText = obj || ''; if (obj && typeof obj != 'string') { objText = syntaxHighlight(JSON.stringify(obj, null, indent)); } text += objText; let pageText = document.querySelector('.output').innerHTML; pageText += (pageText.length > 0 ? '<br />' : '') + text; document.querySelector('.output').innerHTML = pageText; } function syntaxHighlight(json) { if (typeof json != 'string') { json = JSON.stringify(json, undefined, 2); } json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { let cls = 'number'; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'key'; } else { cls = 'string'; } } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; } return '<span class="' + cls + '">' + match + '</span>'; }); } </script> </body> </html><!-- // http://programmingnotes.org/ --> |
QUICK NOTES:
The highlighted lines are sections of interest to look out for.
The code is heavily commented, so no further insight is necessary. If you have any questions, feel free to leave a comment below.
Leave a Reply