使用 bluebird 在顺序流中管理异步操作

Manage async operations in a sequential flow with bluebird

首先,我不会否认我对 promises 很陌生,并试图在我的新 Node.js 应用程序中更好地管理 promises。根据我从朋友和社区那里听到的消息,我正在使用 bluebird。这是一个场景:

该应用程序有一个注册流程,这是一个典型的用例,必须发生以下事件才能注册新用户:

  1. 检查用户是否已经存在。
  2. 如果没有,添加一个新用户。
  3. 发送验证邮件。

我确实有 3 个独立的函数来解决上述每个步骤。

下面是我使用 promises 流程得出的结果...但不知何故我不相信下面的代码:

user.isExistingUser(email)
        .then((successData) => {
            if(successData && successData.length === 0) {
                user.signUp(signUpInfo)
                .then((successData) => {
                    emailService.sendVerificationEmail(recipientInfo)
                    .then((successData) => {
                     res.json(responseUtility.getApiResponse(successData));
                    })
                    .catch((errorObj) => {
                        res.json(responseUtility.getApiResponse(null, null, errorObj));
                    });
                })
                .catch((errorObj) => {
                    res.json(responseUtility.getApiResponse(null, null, errorObj));
                });
            } else {
                res.json(responseUtility.getApiResponse(null, [{
                    param: 'email',
                    msg: 'An account already exists with this email'
                }], null));
            }
        })
        .catch((errorObj) => {
            res.json(responseUtility.getApiResponse(null, null, errorObj));
        });

如您所见,代码似乎有点太长,跟踪起来有点棘手。这里的一些蓝鸟专家可以帮助提供更好或更易读的代码吗?

您应该更好地利用链接。总是 return 从你的函数中承诺做一些异步的事情。

user.isExistingUser(email).then(successData => {
    if (successData && successData.length === 0) {
        return user.signUp(signUpInfo).then(() => {
//      ^^^^^^
            return emailService.sendVerificationEmail(recipientInfo);
//          ^^^^^^
        }).then(successData => {
            res.json(responseUtility.getApiResponse(successData));
        });
    } else {
        res.json(responseUtility.getApiResponse(null, [{
            param: 'email',
            msg: 'An account already exists with this email'
        }], null));
    }
}).catch(errorObj => {
    res.json(responseUtility.getApiResponse(null, null, errorObj));
});

我想实现相同代码的一种合理方式可能是这样;

user.isExistingUser(email)
    .then(successData => successData &&
                         successData.length === 0 ? user.signUp(signUpInfo)
                                                        .then(successData => emailService.sendVerificationEmail(recipientInfo))
                                                        .then(successData => res.json(responseUtility.getApiResponse(successData)))
                                                  :  res.json(responseUtility.getApiResponse(null, [{param: 'email', msg: 'An account already exists with this email'}], null)))
    .catch(err => res.json(responseUtility.getApiResponse(null, null, err)));

不知道你的代码有什么问题

基本上,您的代码是嵌套回调。典型的嵌套回调是这样的:

funcation(args.., 
    function(args..., 
        function(args){...}
    ){...}
){...}

你的是这样的:

function(args).then(()=> {
   function(args).then(() => {
       function(args){...}
   })
})

差别不大,不是吗?


这才是Promise的正确使用方式。

解决你的问题的关键是链接承诺。这其实就是承诺的全部意义。

基本上,您希望 then 回调到 return 一个承诺。这样你就可以链接你的承诺以避免代码的嵌套回调风格。

每个 .then 方法总是 return 本身就是一个承诺。

promiseB = promiseA.then(()=> {
    // callback 
    return promiseC
});

如果callback return 像上面这样的承诺,你可以有效地想到promiseB = promiseC。现在,如果我们有

promiseC = promiseA.then(()=> {
    // callback 
    return promiseB
});
promiseE = promiseC.then(()=> {
    // callback 
    return promiseD
});

您可以将它们链接。那么上面可以缩短为:

promiseA.then(()=> {
    // callback 
    return promiseB
}).then(()=> {
    // callback 
    return promiseD
});

直接回答你的问题。

let userPromise = user.isExistingUser(email); 

// handle new user
userPromise
.then((userData) => {
  assert userData.length === 0;
  // sign up
  // Assuming user.signUp is a Promise with recipientInfo as its resolved value
  return user.signUp(userData.signUpInfo); 
})
.then((recipientInfo)=> {
  // send verification email
  // Assuming user.sentVerificationEmail is a Promise with emailSuccessData as its resolved value
  return user.sentVerificationEmail(recipientInfo)
})
.then((emailSuccessData)=> {
  res.json(responseUtility.getApiResponse(emailSuccessData));
})
// if any of the previous Promise is rejected, 
// the follow .then will be effectively bypassed and fall though to this catch
.catch((err)=> {
  res.json(responseUtility.getApiResponse(null, null, err));
})


// handle existing user
userPromise.then((successData) => {
  assert successData.length > 0
  const json = [{ 
    param: 'email', 
    msg: 'An account already exists with this email'
  }]
  res.json(responseUtility.getApiResponse(null, json, null));
}); 
// no need to catch again. if userPromise is rejected, it will be handled by the catch above.