Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Promise.all with an object as input

I've been working on a small 2D game library for my own use, and I've run into a bit of a problem. There is a particular function in the library called loadGame that takes dependency info as input (resource files, and a list of scripts ot be executed). Here's an example.

loadGame({     "root" : "/source/folder/for/game/",      "resources" : {         "soundEffect" : "audio/sound.mp3",         "someImage" : "images/something.png",         "someJSON" : "json/map.json"     },      "scripts" : [         "js/helperScript.js",         "js/mainScript.js"     ] }) 

Each item in resources has a key that is used by the game to access that particular resource. The loadGame function converts the resources into an object of promises.

The problem is that it tries to use Promises.all to check for when they're all ready, but Promise.all accepts only iterables as inputs - so an object like what I have is out of the question.

So I tried to convert the object into an array, this works great, except each resource is just an element in an array and doesn't have a key to identify them.

Here's the code for loadGame:

var loadGame = function (game) {     return new Promise(function (fulfill, reject) {         // the root folder for the game         var root = game.root || '';          // these are the types of files that can be loaded         // getImage, getAudio, and getJSON are defined elsewhere in my code - they return promises         var types = {             jpg : getImage,             png : getImage,             bmp : getImage,              mp3 : getAudio,             ogg : getAudio,             wav : getAudio,              json : getJSON         };          // the object of promises is created using a mapObject function I made         var resources = mapObject(game.resources, function (path) {             // get file extension for the item             var extension = path.match(/(?:\.([^.]+))?$/)[1];              // find the correct 'getter' from types             var get = types[extension];              // get it if that particular getter exists, otherwise, fail             return get ? get(root + path) :                 reject(Error('Unknown resource type "' + extension + '".'));         });          // load scripts when they're done         // this is the problem here         // my 'values' function converts the object into an array         // but now they are nameless and can't be properly accessed anymore         Promise.all(values(resources)).then(function (resources) {             // sequentially load scripts             // maybe someday I'll use a generator for this             var load = function (i) {                 // load script                 getScript(root + game.scripts[i]).then(function () {                     // load the next script if there is one                     i++;                      if (i < game.scripts.length) {                         load(i);                     } else {                         // all done, fulfill the promise that loadGame returned                         // this is giving an array back, but it should be returning an object full of resources                         fulfill(resources);                     }                 });             };              // load the first script             load(0);         });     }); }; 

Ideally I'd like for some way to properly manage a list of promises for resources while still mantaining an identifier for each item. Any help would be appreciated, thanks.

like image 950
Matt Avatar asked Mar 27 '15 03:03

Matt


People also ask

What is the use of Promise all ()?

all() The Promise. all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will fulfill when all of the input's promises have fulfilled, or if the input iterable contains no promises.

Can a Promise return an object?

The new Promise() constructor returns a promise object. As the executor function needs to handle async operations, the returned promise object should be capable of informing when the execution has been started, completed (resolved) or retuned with error (rejected).

How do I use Promise all with TypeScript?

To use Promise. all() with TypeScript, we can use it as we do with JavaScript. const [foo, bar] = await Promise. all([fooPromise, barPromise]);


1 Answers

First of all: Scrap that Promise constructor, this usage is an antipattern!


Now, to your actual problem: As you have correctly identified, you are missing the key for each value. You will need to pass it inside each promise, so that you can reconstruct the object after having awaited all items:

function mapObjectToArray(obj, cb) {     var res = [];     for (var key in obj)         res.push(cb(obj[key], key));     return res; }  return Promise.all(mapObjectToArray(input, function(arg, key) {     return getPromiseFor(arg, key).then(function(value) {          return {key: key, value: value};     }); }).then(function(arr) {     var obj = {};     for (var i=0; i<arr.length; i++)         obj[arr[i].key] = arr[i].value;     return obj; }); 

Mightier libraries such as Bluebird will also provide this as a helper function, like Promise.props.


Also, you shouldn't use that pseudo-recursive load function. You can simply chain promises together:

….then(function (resources) {     return game.scripts.reduce(function(queue, script) {         return queue.then(function() {             return getScript(root + script);         });     }, Promise.resolve()).then(function() {         return resources;     }); }); 
like image 107
Bergi Avatar answered Sep 18 '22 04:09

Bergi