【问题标题】:Beautiful way to resolve an object with nested promises?用嵌套承诺解决对象的漂亮方法?
【发布时间】:2017-10-19 07:39:44
【问题描述】:

在构建自定义端点时,我经常需要解析包含 Promise 的复杂对象。

举个例子:

给定已知用户的 idemployeeIdmemberGroupsIds(一个数组):

var loginResponse = {
    userprofile : getProfile(id)
    companyInfo : {
        company : getCompany(employeeId)
        companyRelations : getPriviligedInfo(employeeId)
    }
    groups : getGroups(memberGroupsIds)
}

此逻辑适用于仅返回其值的同步函数。但是对于返回承诺的函数,我必须手动将它们全部推送到一个数组中,以确保它们在使用最终对象之前得到解决。

我发现上面的代码很容易理解,我正在寻找一个能够提供其中一些功能的签名,同时仍然确保在将最终对象发送给客户端之前解决承诺。

问题不在于让它工作,而是让它美观且易于阅读。

最好的答案是确保将值返回到对象中的预期键,并且所有承诺都并行解决,同时保持与同步函数的结构有点兼容的结构。

或者,如果我没有抓住重点并且看错了,我应该如何看待它?

【问题讨论】:

  • var loginResponse = Promise.all([getProfile(Id), getCompany(EmployeeId), getPriviligedInfo(EmployeeId), getGroups(MemberGroupsIds)]).then(([userprofile, company, companyRelations, groups]) => ({ userprofile, companyInfo: { company, companyRelations }, groups})); - 当然,你不能只使用loginResponse 作为值,因为它是一个承诺......所以,使用将是loginResponse.then(obj => { ... do things here ... });
  • 是的,这行得通,但并没有真正说明返回对象的结构/如何随着需求的扩展而容易添加新的子对象(想象这用于对象 10乘以示例的大小/复杂性)
  • 显然在一行​​这样它并不漂亮 - 你想要 working 代码还是 pretty 代码 - 猜你可以使用 async/await 来让它漂亮
  • 我想要漂亮的代码 :) 正如我所说,问题不是让它工作,而是让它的语法有点类似于示例对象的语法
  • 不敢相信没有人建议。 IMO,真正的方法是使用一次调用服务器返回您需要的所有数据,而不必等待 4 个单独的调用完成。没有人说漂亮的代码就是聪明的代码。

标签: javascript express promise loopbackjs bluebird


【解决方案1】:

您可以使用下面的辅助函数。它接受一个对象并返回一个当所有嵌套的 Promise 都已解决时解决的 Promise。返回的 Promise 将提供相同的对象作为值,该对象将发生变异,所有嵌入的 Promise 都被相应的值替换。

function promiseRecursive(obj) {
    const getPromises = obj =>
        Object.keys(obj).reduce( (acc, key) =>
            Object(obj[key]) !== obj[key]
                ? acc
                : acc.concat(
                    typeof obj[key].then === "function"
                        ? [[obj, key]]
                        : getPromises(obj[key])
                  )
        , []);
    const all = getPromises(obj);
    return Promise.all(all.map(([obj, key]) => obj[key])).then( responses =>
        (all.forEach( ([obj, key], i) => obj[key] = responses[i]), obj) 
    );
}

你可以这样称呼它:

var loginResponsePromise = promiseRecursive({
    userprofile : getProfile(10),
    companyInfo : {
        company : getCompany(101),
        companyRelations : getPriviligedInfo(101)
    },
    groups : getGroups([5])
});

function promiseRecursive(obj) {
    const getPromises = obj =>
        Object.keys(obj).reduce( (acc, key) =>
            Object(obj[key]) !== obj[key] ? acc
                : acc.concat(typeof obj[key].then === "function" ? [[obj, key]] 
                : getPromises(obj[key]))
        , []);
    const all = getPromises(obj);
    return Promise.all(all.map(([obj, key]) => obj[key])).then( responses =>
        (all.forEach( ([obj, key], i) => obj[key] = responses[i]), obj) 
    );
}

// Example promise-returning functions
const wait = ms => new Promise( resolve => setTimeout(resolve, ms) ),
    getProfile = id => wait(100).then(_ => ({userName: 'user' + id,id})),
    getCompany = employeeId => wait(200).then(_ => ({employeeName: 'employee' + employeeId, employeeId})),
    getPriviligedInfo = employeeId => wait(500).then(_ => ({privs: 'privInfo' + employeeId, employeeId})),
    getGroups = memberGroupsIds => wait(400).then(_ => ({groups: ['group' + memberGroupsIds[0]],memberGroupsIds}));

// Sample input passed to `promiseRecursive` function
const loginResponsePromise = promiseRecursive({
    userprofile : getProfile(10),
    companyInfo : {
        company : getCompany(101),
        companyRelations : getPriviligedInfo(101)
    },
    groups : getGroups([5])
});

// Display the resolved object
loginResponsePromise.then( o => console.log(o) );
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 这看起来很有希望(不是双关语)。我明天会尝试一下,但是对于一个公认的解决方案来说,它是一个强有力的候选人(promiseRecursive 助手的语法对我来说有点困惑,但我会明白的,而且应该可以通过使它更深入递归)。 loginResponse 返回已解析传递对象的承诺,因此不是真正的响应,而是响应的承诺(只是一个命名的东西)。除非有理由不这样做,否则我可能会以全局帮助者的身份实现并执行promiseRecursive({loginObj}).then(o=>loginResponse = o) 之类的操作?
  • 确实,该函数返回一个promise,因此您需要在其上应用then 以获取已解析的对象(另请参见sn-p)。请注意,任何解决方案都必须以异步方式将解析的对象交给您。不可能创建一个以 同步 方式return 结果的函数,因为显然内部承诺只会以异步方式为您提供它们的最终值。您谈到使其递归:它是递归的。不知道你还有什么意思?
  • 谢谢@trincot 是的。我得到了“必须异步返回”,只是重申以帮助我自己的理解 :) 嗯.. 我想我现在看到了递归(我从来没有真正使用过三元运算并且 ES6 缺少括号对我来说仍然有点不寻常)。那里有一段漂亮的小代码!
  • 我现在在文本中更清楚地缩进了三元运算符,并为结果变量提供了一个更好的名称,因此很明显这是一个承诺。
【解决方案2】:

我一般用Bluebird的joinhttp://bluebirdjs.com/docs/api/promise.join.html解决这种场景:

const Promise = require('bluebird');

return Promise.join(
    getProfile(id),
    getCompany(employeeId),
    getPrivilegedInfo(employeeId),
    getGroups(memberGroupsIds),
    (userProfile, company, companyRelations, groups) => {
        return {
            userProfile: userProfile,
            companyInfo: {
                company: company,
                companyRelations: companyRelations
            },
            groups: groups
        };
    }
);

【讨论】:

    【解决方案3】:

    使用新的 ES6 特性,我会这样写:

    Promise.all([
        getProfile(id),
        getCompany(employeeId),
        getPriviligedInfo(employeeId),
        getGroups(memberGroupsIds)
    ])
    .then(response => {
    
        const [ userprofile, company, companyRelations, groups ] = response
    
        const loginResponse = {
          userprofile,
          companyInfo : {
            company,
            companyRelations
          },
          groups
        }
    })
    .catch(err => console.error(err))
    

    也许有趣的部分是Promise.all() 保持输入参数的顺序不取决于哪个先解析。所以在下一步中,使用Destructuring Array assignment,代码看起来是同步的。

    【讨论】:

      猜你喜欢
      • 2021-03-12
      • 2017-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-08
      • 2019-10-28
      • 1970-01-01
      • 2021-01-08
      相关资源
      最近更新 更多