如何正确链接Promise与嵌套

时间:2016-04-06 11:20:30

标签: javascript node.js promise

我的节点项目当前包含一个嵌套的回调圣诞树,以便获取数据并按正确的顺序处理它们。现在我尝试使用Promises进行重构,但我不确定如何正确地进行重构。

让我们说我拿到一份办事处清单,然后为每个办公室提供所有员工,然后是每个员工'薪水。最后,所有实体(办公室,员工和工资)应链接在一起并存储在数据库中。

一些说明我当前代码的伪代码(省略错误处理):

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

我尝试用promises解决这个问题,但我有几个问题:

  • 有点冗长?
  • 每个办公室都需要与他们各自的员工联系,但在saveEmployees职能部门,我只能访问员工,而不是进一步链接的办公室:
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);
});

3 个答案:

答案 0 :(得分:1)

你没有必要筑巢,这也会有效:

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 :(得分:0)

这是改变这个的好方法

        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

问题 - 如果您需要访问多个返回值(即办公室和工作岗位),您基本上有两种选择:

  • 嵌套承诺 - 如果它“有意义”,这通常不是很糟糕。如果逻辑需要,Altought承诺可以避免巨大的回调嵌套,可以嵌套承诺。

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

答案 2 :(得分:0)

您的宣传功能fetchparsesaveOfficessaveEmployees都可以。有了这些,你可以重构你当前的代码,使用promises,chain而不是nest,并省去一堆错误处理样板:

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

在最里面的循环回调中,您可以根据自己的喜好将officeemployeesalary全部链接起来。你无法真正避免这种嵌套。

您将获得大量保存结果数组数组的承诺,或者整个过程中的任何错误。