How to properly link Promises with an attachment

My node project currently contains a side Christmas tree of nested callbacks to receive the data and process it in the correct order. Now I'm trying to use refactoring using Promises, but I'm not sure how to do it right.

Say I am compiling a list of offices, then for each office all of their employees, and then the salary of each employee. In the end, all organizations (offices, employees and salaries) must be connected to each other and stored in a database.

Some pseudo codes illustrating my current code (processing error omitted):

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); }); }); }); }); }); }); }); }); 

I tried to solve this with Promises, but I have a couple of problems:

  • kind of details?
  • each office should be associated with their respective employees, but in the saveEmployees function, I only have access to employees, and not to the office further from the network:

 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); }); 
+6
javascript promise
source share
3 answers

Your promising features fetch , parse , saveOffice s and saveEmployee s are accurate. With them, you can reorganize your current code to use promises, a chain instead of a socket, where applicable, and leave a bunch of error handling patterns:

 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); })); }); })); }); })); }); 

In the loopback callback itself, you have all the office , employee and salary to bind them to your liking. You really cannot escape this kind of nesting.

You will receive a promise for a huge array of arrays of arrays of storage results or for any error in the whole process.

0
source share

You don't have to nest, it will work too:

 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); }); 
+1
source share

Good approach to changing this

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

to that

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

In your example, the result will be the same, but if you do more things (for example, do something in the database), an unexpected result may occur, because without returning the whole method will be executed.

In this example

 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); }); 

has this outlet

 What? It did not end error 

To the question: if you need access to several returned values ​​(for example, offices and employees), you have basically two options:

  • Nested promises are not so bad if it "makes sense". Altought promises does a great job of avoiding a huge reversal of callbacks, normally insert promises if it needs it.

  • Having "global" variables - you can define a variable in the scope of the promise itself and save the results for it, so promises uses these variables as "global" (in its field).

0
source share

All Articles