如何正确链接承诺与嵌套
我的节点项目目前包含一个嵌套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来解决这个问题,但是我遇到了一些问题:
- 有点冗长?
- 每个办公室都需要链接到他们各自的员工,但是在
saveEmployees
function中,我只能访问员工,而不能访问链中的办公室:
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); });
您提供的函数fetch
, parse
, saveOffice
和ssaveEmployee
。 有了这些,你可以重构你当前的代码来使用承诺,在适当的地方使用链而不是嵌套,并且省去一堆error handling样板: s
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中,您可以使用所有office
, employee
和salary
将其链接到您喜欢的位置。 你真的不能避免这种嵌套。
你会得到一个保存结果数组的庞大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用作“全局”(在其范围内)。