如何正确链接承诺与嵌套

我的节点项目目前包含一个嵌套callback的横向圣诞树,以获取数据并按正确的顺序处理它们。 现在我试图重构使用承诺,但我不确定如何正确地做到这一点。

比方说,我要提取一个办公室的名单,然后为每个办公室的所有员工,然后每个员工的工资。 最后,所有的实体(办公室,员工和工资)都应该连在一起并存储在一个数据库中。

一些说明我当前代码的伪代码(省略了error handling):

fetch(officesEndpoint, function (data, response) { parse(data, function (err, offices) { offices.forEach(function (office) { save(office); fetch(employeesEndPoint, function (data, response) { parse(data, function (err, employees) { // link each employee to office save(office); save(employee); employees.forEach(function () { fetch(salaryEndpoint, function (data, response) { parse(data, function (err, salaries) { // link salary to employee save(employee); }); }); }); }); }); }); }); }); 

我试着用promise来解决这个问题,但是我遇到了一些问题:

  • 有点冗长?
  • 每个办公室都需要链接到他们各自的员工,但是在saveEmployeesfunction中,我只能访问员工,而不能访问链中的办公室:

 var restClient = require('node-rest-client'); var client = new restClient.Client(); var xml2js = require('xml2js'); // some imaginary endpoints var officesEndpoint = 'http://api/offices'; var employeesEndpoint = 'http://api/offices/employees'; var salaryEndpoint = 'http://api/employees/:id/salary'; function fetch (url) { return new Promise(function (resolve, reject) { client.get(url, function (data, response) { if (response.statusCode !== 200) { reject(statusCode); } resolve(data); }); }); } function parse (data) { return new Promise(function (resolve, reject) { xml2js.parseString(data, function (err, result) { if (err) { reject(err); } resolve(result); }); }); } function saveOffices (offices) { var saveOffice = function (office) { return new Promise(function (resolve, reject) { setTimeout(function () { // simulating async save() console.log('saved office in mongodb'); resolve(office); }, 500); }) } return Promise.all(offices.map(saveOffice)); } function saveEmployees (employees) { var saveEmployee = function (employee) { return new Promise(function (resolve, reject) { setTimeout(function () { // simulating async save() console.log('saved employee in mongodb'); resolve(office); }, 500); }) } return Promise.all(offices.map(saveEmployee)); } fetch(officesEndpoint) .then(parse) .then(saveOffices) .then(function (savedOffices) { console.log('all offices saved!', savedOffices); return savedOffices; }) .then(function (savedOffices) { fetch(employeesEndPoint) .then(parse) .then(saveEmployees) .then(function (savedEmployees) { // repeat the chain for fetching salaries? }) }) .catch(function (error) { console.log('something went wrong:', error); }); 

您提供的函数fetchparsesaveOffice ssaveEmployee s 。 有了这些,你可以重构你当前的代码来使用承诺,在适当的地方使用链而不是嵌套,并且省去一堆error handling样板:

 fetch(officesEndpoint) .then(parse) .then(function(offices) { return Promise.all(offices.map(function(office) { return save(office) .then(function(){ return fetch(employeesEndPoint); }) .then(parse) .then(function(employees) { // link each employee to office // throw in a Promise.all([save(office), save(employee)]) if needed here return Promise.all(employees.map(function(employee) { return fetch(salaryEndpoint) .then(parse) .then(function(salaries) { return Promise.all(salaries.map(function(salary) { // link salary to employee return save(employee); })); }); })); }); })); }); 

在最内层的循环callback中,您可以使用所有officeemployeesalary将其链接到您喜欢的位置。 你真的不能避免这种嵌套。

你会得到一个保存结果数组的庞大arrays的承诺,或在整个过程中的任何错误。

你不需要嵌套,这也会工作:

 fetch(officesEndpoint) .then(parse) .then(saveOffices) .then(function(savedOffices) { console.log('all offices saved!', savedOffices); return savedOffices; }) .then(function(savedOffices) { // return a promise return fetch(employeesEndPoint); // the returned promise can be more complex, like a Promise.all of fetchEmployeesOfThisOffice(officeId) }) // so you can chain at this level .then(parse) .then(saveEmployees) .then(function(savedEmployees) { return fetch(salariesEndPoint); }) .catch(function(error) { console.log('something went wrong:', error); }); 

改变这个是很好的方法

  if (response.statusCode !== 200) { reject(statusCode); } resolve(data); 

对此

  if (response.statusCode !== 200) { return reject(statusCode); } resolve(data); 

在你的例子中,结果将是相同的,但是如果你做更多的事情(如在数据库中做某事)可能会出现意外的结果,因为没有返回,整个方法将被执行。

这个例子

 var prom = new Promise((resolve,reject) => { reject(new Error('error')); console.log('What? It did not end'); resolve('Ok, promise will not be called twice'); }); prom.then(val => { console.log(val); }).catch(err => { console.log(err.message); }); 

有这个输出

 What? It did not end error 

对于这个问题 – 如果你需要访问多个返回值(即办公室和雇员),你基本上有两个select:

  • 嵌套承诺 – 如果“合理”,这通常不是坏事。 Altought的承诺是非常好的,以避免巨大的callback嵌套,如果逻辑需要它可以嵌套承诺。

  • 拥有“全局”variables – 您可以在承诺范围内定义variables并将结果保存到variables中,因此承诺将这些variables用作“全局”(在其范围内)。