带承诺的简单请求链逻辑的实现

时间:2018-11-22 15:54:38

标签: javascript promise es6-promise

我正在尝试实现以下逻辑的虚拟仿真:

enter image description here

但是我不确定我是否完全了解如何做的最佳实践。 该任务的重点是避免触发冗余catch块回调。 IMO如果第一个请求失败,则随后的所有代码应停止。

我的意思是:如果第一个请求失败,那么我们不会发出第二个请求,也不会调用第二个请求promise的catch块。

简而言之,我正在寻找一种非常干净,简单的解决方案,如下所示:

firstRequest()
    .then(r => {
        console.log('firstRequest success', r);
        return secondRequest();
    }, e => console.log('firstRequest fail', e))
    .then(r => {
        console.log('secondRequest success', r);
        // Should I return something here? Why?
    }, e => console.log('secondRequest fail', e));

我写了以下实现。如果两个请求都成功,并且第二个请求失败,则按预期方式工作。但是,如果第一个请求失败(如您所见,两个catch块都在触发),它就会出错。您可以使用isFirstSucceedisSecondSucceed标志进行检查。

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
    if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
  return getUserById();
}, e => {
  console.error('1st request failed', e);
  throw e;
})
.then(
  r => console.info('2nd request succeed', r), 
  e => {
    console.error('2nd request failed', e);
    throw e;
});

我可以将第二个请求的then移到第一个请求的then,但看起来很丑。

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
	if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
    getUserById().then(
      r => console.info('2nd request succeed', r), 
      e => {
        console.error('2nd request failed', e);
        throw e;
      });
}, e => {
    console.error('1st request failed', e);
    throw e;
})

问题:

  • 如何根据所有承诺的最佳实践来实现所描述的逻辑?
  • 是否可以在每个throw e块中避免使用catch
  • 我应该使用es6 Promises吗?还是最好使用一些Promise库?
  • 还有其他建议吗?

6 个答案:

答案 0 :(得分:2)

您的流程图是您要实现的逻辑,但并不是完全如何实现诺言。问题是,没有办法告诉一个承诺链在这里只是“结束”,并且在链的后面不会调用任何其他.then().catch()处理程序。如果您在链中遭到拒绝并被拒绝,它将调用链中的下一个.catch()处理程序。如果您在本地处理拒绝并且不重新抛出,则它将调用链中的下一个.then()处理程序。这些选项都不完全符合您的逻辑图。

因此,您必须在思维上改变对逻辑图的看法,以便可以使用承诺链。

最简单的选择(可能对90%的诺言链使用的是)只在链的末尾放置一个错误处理程序。链中任何地方的任何错误都将跳至链末尾的单个.catch()处理程序。仅供参考,在大多数情况下,我发现使用.catch()的代码比.then()的第二个参数更具可读性,因此这就是我在此处显示的方式

firstRequest().then(secondRequest).then(r => {
    console.log('both requests successful');
}).catch(err => {
    // An error from either request will show here 
    console.log(err);
});

当您提供一个catch块并且您既不返回被拒绝的诺言也不重新抛出错误时,诺言基础结构会认为您已“处理”了诺言,因此解决方案链将继续进行。如果您抛出该错误,则将触发下一个catch块,并且将跳过所有介入的.then()处理程序。

您可以利用它来在本地捕获错误,执行某些操作(例如记录日志),然后将其重新抛出以保持承诺链被拒绝。

firstRequest().catch(e => {
     console.log('firstRequest fail', e));
     e.logged = true;
     throw e;
}).then(r => {
    console.log('firstRequest success', r);
    return secondRequest();
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    if (!e.logged) {
        console.log('secondRequest fail', e));
    }
});

或者,该版本使用调试消息标记错误对象,然后重新抛出该错误,然后只能将错误记录在一个位置:

firstRequest().catch(e => {
     e.debugMsg = 'firstRequest fail';
     throw e;
}).then(r => {
    console.log('firstRequest success', r);
    return secondRequest().catch(e => {
        e.debugMsg = 'secondRequest fail';
        throw e;
    });
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    console.log(e.debugMsg, e);
});

我什至遇到过一些辅助功能为我节省了一些代码和一些视觉上的复杂性的情况,特别是如果链中有很多这样的情况:

function markErr(debugMsg) {
    return function(e) {
        // mark the error object and rethrow
        e.debugMsg = debugMsg;
        throw e;
    }
}

firstRequest()
  .catch(markErr('firstRequest fail'))
  .then(r => {
    console.log('firstRequest success', r);
    return secondRequest().catch(markErr('secondRequest fail'));
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    console.log(e.debugMsg, e);
});

分别回答每个问题:

  

如何根据所有承诺的最佳实践来实现所描述的逻辑?

如上所述。我会说最简单和最佳的做法是我展示的第一个代码块。如果您需要确保在到达最后一个.catch()时遇到一个唯一可识别的错误,从而知道是哪个错误导致的,则可以将每个函数中被拒绝的错误修改为唯一的,这样您就可以分辨出哪个错误来自最后一个.catch()块。如果您无法修改这些功能,则可以使用包装器包装它们,以捕获并标记它们的错误,或者可以使用我展示的markErr()类型的解决方案内联地进行包装。在大多数情况下,您只需要知道有错误,而不是确切的步骤,通常就不必知道链中的每个步骤。

  

是否可以避免在每个捕获块中抛出e?

这取决于。如果错误对象已经是唯一的,那么最后只能使用一个.catch()。如果错误对象不是唯一的,但您需要知道哪个确切步骤失败了,则必须在每个步骤中使用.catch(),以便可以唯一地标记错误,或者需要修改链中的每个函数有一个独特的错误。

  

我应该使用es6 Promises吗?

是的。我知道的更好的方法。

  

还是使用一些Promise库更好?

我不知道Promise库中的任何功能都可以简化此操作。这实际上与您要如何报告错误以及每个步骤是否在定义唯一错误有关。 Promise库无法真正为您做到这一点。

  

还有其他建议吗?

继续学习如何将诺言转化为每个问题的解决方案。

答案 1 :(得分:1)

IMO,您可以使用async / await ...仍然可以保证,但是看起来要干净得多。这是我关于上述逻辑的示例方法。

function firstRequest() {
   return new Promise((resolve, reject) =>  {
       // add async function here
       // and resolve("done")/reject("err")
    });
 } 

function secondRequest() {
   return new Promise((resolve, reject) =>  {
       // add async function here
       // and resolve("done")/reject("err")
    });
}

async function startProgram() { 
   try { 
       await firstRequest();
       await secondRequest();
   } catch(err) { 
       console.log(err); 
       goToEndFn();
   }
}

startProgram(); // start the program

答案 2 :(得分:1)

https://github.com/xobotyi/await-of

$ npm i --save await-of 

import of from "await-of";

async () => {
    let [res1, err1] = await of(axios.get('some.uri/to/get11'));
    let [res2, err2] = await of(axios.get('some.uri/to/get22'));

    if (err1) {
       console.log('err1', err1)
    }
    if (err2) {
       console.log('err2', err2)
    }
    console.log('res1', res1)
    console.log('res2', res2)

};

答案 3 :(得分:0)

可能是异步/等待?

async function foo() {
    try {
        const firstResult = await firstRequest();
        const secondResult = await secondRequest();
    } catch(e) {
        // e = either first or second error
    }
}

在此代码中,第一个请求的错误将控制权转移到catch块,第二个请求将无法启动

  

我应该使用es6 Promises吗?

可能是的,直到您完全确定您的代码用于过时的环境中为止。他们已经不是那么新而浮华了

答案 4 :(得分:0)

您不需要为每一个承诺处理错误

您只需要将处理错误作为常见错误

这样做:

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej();
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
    if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
  return getUserById();
})
.then(r => {
  console.info('2st request succeed', r);
  return;
})
.catch((e) => {
    console.error('request failed', e);
    throw new Error(e);
})

答案 5 :(得分:0)

您可以滥用鸭子输入法来用<mat-form-field class="formfield-size-medium"> <mat-select [formControlName]="formControl.nationality" name="Nationality" placeholder="Nationalities" class="class-mat-select" multiple> <mat-option *ngFor="let nationality of nationalityList.nationalities" [value]="nationality.value"> {{getNationalityValue(nationality.value)}} </mat-option> </mat-select> </mat-form-field> 终止承诺链。我在此行return { then: function() {} };

之后修改了您的代码

console.error('1st request failed', e);