在node.js中嵌套承诺是正常的吗?

在学习node.js的过程中,我一直在努力学习两个星期的问题是如何使用节点进行同步编程。 我发现,无论我如何尝试按顺序做事,总是会得到嵌套的承诺。 我发现有一些模块,比如Q,可以帮助保证链接的可维护性。 在做研究的时候,我不明白的是Promise.all()和Promise.resolve()和Promise.reject()。 Promise.reject与名称相差无几,但在编写应用程序时,我很困惑如何将这些函数或对象中的任何一个包含进来,而不会破坏应用程序的行为。 当来自Java或C#等编程语言时,node.js肯定有学习曲线。 仍然存在的问题是在node.js中承诺链接是否正常(最佳实践)。

driver.get('https://website.com/login').then(function () { loginPage.login('company.admin', 'password').then(function () { var employeePage = new EmployeePage(driver.getDriver()); employeePage.clickAddEmployee().then(function() { setTimeout(function() { var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); addEmployeeForm.insertUserName(employee.username).then(function() { addEmployeeForm.insertFirstName(employee.firstName).then(function() { addEmployeeForm.insertLastName(employee.lastName).then(function() { addEmployeeForm.clickCreateEmployee().then(function () { employeePage.searchEmployee(employee); }); }); }); }); }, 750); }); }); }); 

不,Promises的最大好处之一就是你可以保持你的asynchronous代码是线性的而不是嵌套的(从继续传递风格的callback地狱)。

承诺给你返回语句和错误抛出,你失去了延续传球的风格。

在你的情况下,你需要从你的承诺返回的承诺返回的function,所以你可以链接你的承诺。

这是一个快速重构,说明链接:

 driver.get('https://website.com/login') .then(function() { return loginPage.login('company.admin', 'password') }) .then(function() { var employeePage = new EmployeePage(driver.getDriver()); return employeePage.clickAddEmployee(); }) .then(function() { setTimeout(function() { var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); addEmployeeForm.insertUserName(employee.username) .then(function() { return addEmployeeForm.insertFirstName(employee.firstName) }) .then(function() { return addEmployeeForm.insertLastName(employee.lastName) }) .then(function() { return addEmployeeForm.clickCreateEmployee() }) .then(function() { return employeePage.searchEmployee(employee); }); }, 750); }); 

Promise.all需要一系列的承诺,一旦所有的承诺解决,如果有任何被拒绝,数组被拒绝。

例:

 addEmployeeForm.insertUserName(employee.username) .then(function() { //these two functions will execute in parallel return Promise.all([ addEmployeeForm.insertFirstName(employee.firstName), addEmployeeForm.insertLastName(employee.lastName) ]) //this will be called after both insertFirstName and insertLastName have suceeded .then(function() { return addEmployeeForm.clickCreateEmployee() }) .then(function() { return employeePage.searchEmployee(employee); }) //if an error arises anywhere in the chain this function will be invoked .catch(function(err){ console.log(err); }); 

Promise.resolve()和Promise.reject()是从asynchronous函数创buildPromise时使用的方法。 解决将解决/ fullfil的承诺(这意味着一个链接的方法将被调用的结果值)拒绝将拒绝承诺(这意味着一个链接的方法将不会被调用,但链接的catch方法将被调用的错误出现)。

另外,你为什么包装SetTimeout函数?

使用async库,并使用async.series而不是嵌套的链接,这看起来真的难看,很难debugging/理解。

 async.series([ methodOne, methodTwo ], function (err, results) { // Here, results is the value from each function console.log(results); }); 

Promise.all(iterable)方法返回一个promise,当迭代参数中的所有promise都解决了,或者拒绝了第一个被拒绝的promise的原因。

 var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "foo"); }); Promise.all([p1, p2, p3]).then(function(values) { console.log(values); // [3, 1337, "foo"] }); 

Promise.resolve(value)方法返回一个用给定值parsing的Promise对象。 如果价值是一个可行的(即有一个方法),返回的承诺将“跟随”,那么采用它的最终状态; 否则返还的承诺将以价值实现。

 var p = Promise.resolve([1,2,3]); p.then(function(v) { console.log(v[0]); // 1 }); 

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

我删除了不必要的嵌套。 我使用“蓝鸟”的语法(我的首选Promise库) http://bluebirdjs.com/docs/api-reference.html

 var employeePage; driver.get('https://website.com/login').then(function() { return loginPage.login('company.admin', 'password'); }).then(function() { employeePage = new EmployeePage(driver.getDriver()); return employeePage.clickAddEmployee(); }).then(function () { var deferred = Promise.pending(); setTimeout(deferred.resolve,750); return deferred.promise; }).then(function() { var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); return Promise.all([addEmployeeForm.insertUserName(employee.username), addEmployeeForm.insertFirstName(employee.firstName), addEmployeeForm.insertLastName(employee.lastName)]); }).then(function() { return addEmployeeForm.clickCreateEmployee(); }).then(function() { return employeePage.searchEmployee(employee); }).catch(console.log); 

我修改了你的代码来包含你所有问题的例子。

  1. 使用promise时,不需要使用asynchronous库。 承诺是一个非常强大的自己,我认为它是一个反模式混合承诺和像asynchronous库。

  2. 通常你应该避免使用var deferred = Promise.pending()风格…除非

当包装不符合标准约定的callbackAPI时。 像setTimeout:'

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

对于setTimeout示例..创build一个“延迟”承诺…解决setTimeout内的承诺,然后在setTimeout之外返回承诺。 这似乎有点不直观。 看这个例子,我回答了另一个问题。 Q.js承诺与节点。 缺less`socket`上的error handling程序。 TypeError:不能调用未定义的方法

通常情况下,您可以使用Promise.promisify(someFunction)将callbacktypes函数转换为Promise返回函数。

  1. Promise.all让我们假设你正在多次调用一个asynchronous返回的服务。 如果他们不相互依赖,你可以同时打电话。

只需将函数调用作为一个数组传递即可。 Promise.all([promiseReturningCall1,promiseReturningCall2,promiseReturningCall3]);

  1. 最后添加一个catch块到最后..确保你捕获任何错误。 这将捕捉链中任何地方的任何exception。

我刚刚回答了一个类似的问题 ,我解释了一种使用发电机以很好的方式压扁Promise链的技术。 该技术从协同工作中获得灵感。

采取这一点的代码

 Promise.prototype.bind = Promise.prototype.then; const coro = g => { const next = x => { let {done, value} = g.next(x); return done ? value : value.bind(next); } return next(); }; 

使用它,你可以将你的深层嵌套的Promise链变成这个

 coro(function* () { yield driver.get('https://website.com/login') yield loginPage.login('company.admin', 'password'); var employeePage = new EmployeePage(driver.getDriver()); yield employeePage.clickAddEmployee(); setTimeout(() => { coro(function* () { var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); yield addEmployeeForm.insertUserName(employee.username); yield addEmployeeForm.insertFirstName(employee.firstName); yield addEmployeeForm.insertLastName(employee.lastName); yield addEmployeeForm.clickCreateEmployee(); yield employeePage.searchEmployee(employee); }()); }, 750); }()); 

使用命名的生成器,我们可以使其更清晰

 // don't forget to assign your free variables // var driver = ... // var loginPage = ... // var employeePage = new EmployeePage(driver.getDriver()); // var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); // var employee = ... function* createEmployee () { yield addEmployeeForm.insertUserName(employee.username); yield addEmployeeForm.insertFirstName(employee.firstName); yield addEmployeeForm.insertLastName(employee.lastName); yield addEmployeeForm.clickCreateEmployee(); yield employeePage.searchEmployee(employee); } function* login () { yield driver.get('https://website.com/login') yield loginPage.login('company.admin', 'password'); yield employeePage.clickAddEmployee(); setTimeout(() => coro(createEmployee()), 750); } coro(login()); 

但是,这只能使用协程来控制承诺的stream程。 阅读我上面链接的答案,展示了这种技术的其他一些优点和function。

如果你打算使用协程来达到这个目的,我鼓励你去看看co库 。

希望这可以帮助。

PS不知道你为什么用这种方式使用setTimeout 。 什么是等待750毫秒的具体点?

你的下一步是从嵌套到链接。 你需要认识到,每一个承诺都是一个孤立的承诺,可以链接在父母的承诺。 换句话说,你可以把承诺扁平化。 每个承诺结果可以传递给下一个。

这是一个很好的博客文章: Flattening Promise Chains 。 它使用Angular,但你可以忽略它,并看看如何深度嵌套的承诺变成一个链。

另一个好的答案就在这里StackOverflow: 理解JavaScript的承诺; 堆栈和链接 。

你可以像这样链接承诺:

 driver.get('https://website.com/login').then(function () { return loginPage.login('company.admin', 'password') )}.then(function () { var employeePage = new EmployeePage(driver.getDriver()); return employeePage.clickAddEmployee().then(function() { setTimeout(function() { var addEmployeeForm = new AddEmployeeForm(driver.getDriver()); return addEmployeeForm.insertUserName(employee.username).then(function() { retun addEmployeeForm.insertFirstName(employee.firstName) }).then(function() { return addEmployeeForm.insertLastName(employee.lastName) }).then(function() { return addEmployeeForm.clickCreateEmployee() }).then(function () { retrun employeePage.searchEmployee(employee); })}, 750); }); 

}); });