使用可读的函数名称扁平化承诺链

Flattening promise chain with readable function name

我在 Handling multiple catches in promise chain 中看到了 promise 实现,它产生了一个非常可读的链

return validateInput
                 .then(checkLoginPermission)
                 .then(checkDisableUser)
                 .then(changePassword);

但是,为了做到这一点,每个函数都需要 return 一个值而不是 Promise?由于 Promise 可以解析为值或 Promise,所以这不是问题。我的目标是让每个函数都具有可读的清晰逻辑。

尝试展开嵌套的 promise 函数时出现问题

return validateInput
       .then(function(resultA) {
            return checkLoginPermission
               .then (function(resultB) {
                   // Do something with resultA
               })
       });

想象一下原始实现涉及访问先前承诺的值。有了嵌套的承诺,它很容易实现。但是对于展平链,我需要像这样分解每个函数

function validateInput = function (resultA ) {
        return Promise.resolve({resultA : resultA, resultB : 
}
function checkLoginPermission = function (mix ) {
        let resultA = mix.resultA;
        let resultB = mix.resultB
        //Do something with resultA
        ... 
}

当链中的最后一个函数从一开始就依赖某些东西时,情况会更糟。这意味着即使未使用该值,也必须从链的开头向下传递。

所以我是不是不小心踩到了某种可能影响性能的反模式?没有所有这些麻烦,我还能如何实现良好的可读性?

这实际上是 asyncawait 发挥作用的地方。当您需要跨多个异步 calls/promises 的结果在范围内时,这很好。如果你能用那个,我会说试试。

async function foo () {
    const input = await validateInput()
    const hasPermission = await checkLoginPermission(input)
    const result = await checkDisableUser(hasPermission)
    return await changePassword(result)
}

只需将变量传递给需要的函数即可。只是在那里展示一个例子。我也有点不确定您如何设置 validateInput,我认为您需要将 await 放在函数调用本身的前面。

如果您不能使用 async/await,我通常使用您的第二个代码片段,或者在顶部定义更高范围的变量:

let resultA
return validateInput
   .then(function(result) {
        resultA = result
        return checkLoginPermission
           .then (function(resultB) {
               // Do something with resultA
           })
   });

Promises 是一种模式,与函数式编程有关,直接将数据从一个函数传递到另一个函数是最基本的(称为 compose,这里示例:http://scott.sauyet.com/Javascript/Talk/Compose/2013-05-22/)。所以这绝不是反模式。

我看不出这种模式有什么问题。您可以将任何您想要的数据传递给下一个 Promise,并在嵌套的 Promises 中获取它们需要的数据。它非常透明和清晰:

function validateInput() {
    return Promise.resolve({resultA: 1});
}

function checkLoginPermission(result) {
    return new Promise(function(resolve, reject) {
        // ...
        // code
        // ...
        result.resultB = 2;
        return resolve(result);
    });
}

function checkDisableUser(result) {
    return new Promise(function(resolve, reject) {
        // grab some data from previous function
        let resultB = result.resultB;
        // ...
        // code
        // ...
        result.resultC = 3;
        return resolve(result);
    });
}

function changePassword(result) {
    return new Promise(function(resolve, reject) {
        // grab some data from previous functions
        let resultB = result.resultB;
        let resultC = result.resultC;
        // ...
        // code
        // ...
        result.resultD = resultB * resultC;
        return resolve(result);
    });
}

validateInput()
    .then(checkLoginPermission)
    .then(checkDisableUser)
    .then(changePassword);

您还可以在一些变量中收集数据,在 Promises 之前声明,这样您就不必传递结果。但它会破坏 Promise 的功能性。

内部 .then(/* ... */) 回调可以 return 原始值或解析为某个值的 Promise。如果它是另一个承诺,那么下一个 .then 将不会开始,直到内部承诺得到解决。本质上,Promise 总是解析为非 promise 类型。如果你 resolve 或 return 另一个 Promise,它将自动展开。

我想提出一个使用 ramda.js#pipeP() 的解决方案。

这个函数的好处是它按顺序解析承诺。

我们可以使用 pipeP():

重写您的示例
import pipeP from 'ramda/src/pipeP'

pipeP([
  checkLoginPermission,
  checkDisableUser,
  changePassword
])(initialValue)
.then(responseChangePassword => { ... })

前一个 promise 的结果传递给下一个。