开发者问题收集

JavaScript 承诺未按顺序触发

2018-07-18
154

我一直在尝试有效地管理如何在 Express.js 应用中构建承诺。

目前,我有以下场景:在注册过程中,用户有一个可选的 组织名称 字段。如果填写了该字段,我需要创建 Organization 对象,然后将其 _id 添加到我要应用于 user 的其他信息中。如果没有 Organization name ,请继续并更新基本 user 信息。

<-- 目前,在创建 organization 之前正在更新 user 。 -->

//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;

//if organization name is passed, create object and store _id in user object. 
let create_organization = new Promise(function(resolve, reject) {
  if (req.body.organization_name !== "") { //check if name is sent from form
    Organization.create({
      name: req.body.organization_name
    }, function(err, result) {
      console.log(result);
      if (!err) {
        info['local.organization'] = result._id;
        resolve()
      } else {
        reject()
      }
    })
  } else {
    resolve()
  }
});

let update_user = new Promise(function(resolve, reject) {
  User.update({
    _id: req.user._id
  }, info, function(err, result) {
    if (!err) {
      console.log("Updated User!"); < --prints before Organization is created
      resolve();
    } else {
      reject();
    }
  })
});

create_organization
  .then(function() {
    return update_user;
  })
  .then(function() {
    res.redirect('/dash');
  })
2个回答

您的代码中没有任何内容会等待第一个 Promise 解决后再继续执行后续工作。只要您调用 User.update ,工作就会立即开始,而当您在 Promise 执行器中使用该代码调用 new Promise 时,这项工作会同步完成。

相反,请等到上一个 Promise 解决后再执行此操作。我会通过将这些函数包装在可重复使用的支持承诺的包装器( createOrganizationupdateUser )中来实现此目的:

// Reusable promise-enabled wrappers
function createOrganization(name) {
    return new Promise(function(resolve, reject) {
        Organization.create({name: name}, function(err, result) {
            console.log(result);
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

function updateUser(id, info) {
    return new Promise(function(resolve, reject) {
        User.update({_id: id}, info, function(err, result) {
            if (err) {
                reject(err);
            } else {
                resolve();
            }
        })
    });
}

(您可能能够使用 util.promisify promisify npm 模块来避免手动执行此操作。)

然后:

//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;

//if organization name is passed, create object and store _id in user object. 
(req.body.organization_name === "" ? Promise.resolve() : createOrganization(req.body.organization_name))
.then(function() {
    return updateUser(req.user._id, info);
})
.catch(function(error) {
    // handle/report error
});

(我坚持使用 ES5 级语法,因为您的代码似乎正在这样做……)

T.J. Crowder
2018-07-18

请注意,传入 Promise 构造函数的所谓“执行器函数” 立即调用 。这就是为什么您在两个远程过程调用之间本质上存在竞争条件的原因。要解决此问题,请让更新负责创建承诺:

function updateUser(userId, userInfo) {
  return new Promise(function(resolve, reject) {
    User.update({_id: userId}, userInfo, function(err, result) {
      if (err) { 
        reject(err);
      }
      else {
        resolve(result);
      }
    });
  });
}

... 并在 then() 中调用此函数。通过这样做,只有在调用 updateUser 时才会调用执行器函数 - 并且这将在 createOrganization() 完成其工作之后。

raina77ow
2018-07-18