如何将Promise.all与对象一起用作输入

Mat*_*att 22 javascript asynchronous promise

我一直在为我自己使用的小型2D游戏库工作,我遇到了一些问题.库中有一个名为loadGame的特定函数,它将依赖信息作为​​输入(资源文件和要执行的脚本列表).这是一个例子.

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"
    ]
})
Run Code Online (Sandbox Code Playgroud)

资源中的每个项目都有一个密钥,游戏使用该密钥来访问该特定资源.loadGame函数将资源转换为promises对象.

问题是它试图使用Promises.all来检查它们何时准备就绪,但是Promise.all只接受迭代作为输入 - 所以像我所拥有的对象是不可能的.

所以我尝试将对象转换为数组,这很有效,除了每个资源只是数组中的一个元素,并且没有用于标识它们的键.

这是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);
        });
    });
};
Run Code Online (Sandbox Code Playgroud)

理想情况下,我想以某种方式正确管理资源的承诺列表,同时仍然保留每个项目的标识符.任何帮助将不胜感激,谢谢.

小智 12

如果你使用lodash库,你可以通过单行函数来实现:

Promise.allValues = async (object) => {
  return _.zipObject(_.keys(object), await Promise.all(_.values(object)))
}
Run Code Online (Sandbox Code Playgroud)

  • 请不要修改诸如Promise之类的内置对象。 (3认同)
  • 此代码片段假设“values(obj)”和“keys(obj)”以相同的顺序返回条目 (2认同)

Ber*_*rgi 11

首先:废弃Promise构造函数,这个用法是反模式的!


现在,针对您的实际问题:正确识别后,您错过了每个值的关键字.您需要在每个promise中传递它,以便在等待所有项目后重建对象:

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;
});
Run Code Online (Sandbox Code Playgroud)

像Bluebird这样的更强大的库也会将其作为辅助函数提供Promise.props.


此外,您不应该使用该伪递归load函数.您可以简单地将承诺链接在一起:

….then(function (resources) {
    return game.scripts.reduce(function(queue, script) {
        return queue.then(function() {
            return getScript(root + script);
        });
    }, Promise.resolve()).then(function() {
        return resources;
    });
});
Run Code Online (Sandbox Code Playgroud)


Mar*_*man 9

我实际上为此创建了一个库并将其发布到 github 和 npm:

https://github.com/marcelowa/promise-all-properties
https://www.npmjs.com/package/promise-all-properties

唯一的事情是您需要为对象中的每个承诺分配一个属性名称......这是自述文件中的一个示例

import promiseAllProperties from 'promise-all-properties';

const promisesObject = {
  someProperty: Promise.resolve('resolve value'),
  anotherProperty: Promise.resolve('another resolved value'),
};

const promise = promiseAllProperties(promisesObject);

promise.then((resolvedObject) => {
  console.log(resolvedObject);
  // {
  //   someProperty: 'resolve value',
  //   anotherProperty: 'another resolved value'
  // }
});
Run Code Online (Sandbox Code Playgroud)


Mat*_*OFF 9

这是 @Matt 的答案,其中包含一些类型和一些重命名,并使用ECMA-2019 Object.fromEntries

// delayName :: (k, Promise a) -> Promise (k, a)
const delayName = ([name, promise]) => promise.then((result) => [name, result]);

export type PromiseValues<TO> = {
    [TK in keyof TO]: Promise<TO[TK]>;
};

// promiseObjectAll :: {k: Promise a} -> Promise {k: a}
export const promiseObjectAll = <T>(object: PromiseValues<T>): Promise<T> => {
    const promiseList = Object.entries(object).map(delayName);
    return Promise.all(promiseList).then(Object.fromEntries);
};

Run Code Online (Sandbox Code Playgroud)


Tom*_*thy 9

function resolveObject(obj) {
    return Promise.all(
      Object.entries(obj).map(async ([k, v]) => [k, await v])
    ).then(Object.fromEntries);
}
Run Code Online (Sandbox Code Playgroud)

感谢 Cyril Auburtin 的天才:https://esdiscuss.org/topic/modify-promise-all-to-accept-an-object-as-a-parameter

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

async function test() {
    console.time(1);
    console.log(await resolveObject({
        foo: delay(5).then(()=>1),
        bar: delay(120).then(()=>2)
    }));
    console.timeEnd(1);
}
Run Code Online (Sandbox Code Playgroud)

  • 如果有人有兴趣获取 `resolveObject` 返回值的类型,可以使用 `functionsolveObject&lt;T extends Record&lt;keyof T, any&gt;&gt;(obj: T): Promise&lt;{ [K in keyof T]: Awaited&lt;T[ K]&gt; }&gt;` (2认同)

Zak*_*nry 7

这是一个简单的 ES2015 函数,它接受一个具有可能是 Promise 的属性的对象,并返回具有已解析属性的该对象的 Promise。

function promisedProperties(object) {

  let promisedProperties = [];
  const objectKeys = Object.keys(object);

  objectKeys.forEach((key) => promisedProperties.push(object[key]));

  return Promise.all(promisedProperties)
    .then((resolvedValues) => {
      return resolvedValues.reduce((resolvedObject, property, index) => {
        resolvedObject[objectKeys[index]] = property;
        return resolvedObject;
      }, object);
    });

}
Run Code Online (Sandbox Code Playgroud)

用法:

promisedProperties({a:1, b:Promise.resolve(2)}).then(r => console.log(r))
//logs Object {a: 1, b: 2}

class User {
  constructor() {
    this.name = 'James Holden';
    this.ship = Promise.resolve('Rocinante');
  }
}

promisedProperties(new User).then(r => console.log(r))
//logs User {name: "James Holden", ship: "Rocinante"}
Run Code Online (Sandbox Code Playgroud)

请注意,@Bergi 的答案将返回一个新对象,而不是改变原始对象。如果您确实想要一个新对象,只需将传递到reduce函数的初始值设定项值更改为{}