避免javascript回调和承诺地狱

时间:2016-08-09 23:31:01

标签: javascript node.js promise

我有许多异步方法要执行,我的程序流可以根据每个方法返回而改变很多。下面的逻辑就是一个例子。我无法使用Promises以易于阅读的方式编写它。你会怎么写的?

Ps:欢迎更复杂的流程。

Ps2:is_business是一个预定义的标志,我们说我们是否在写一个"业务用户"或者#34; person user"。

begin transaction
update users
if updated
    if is_business
        update_business
        if not updated
            insert business
        end if
    else
        delete business
    end if
else
    if upsert
        insert user
        if is_business
            insert business
        end if
    end if
end if
commit transaction

2 个答案:

答案 0 :(得分:4)

Promises的好处在于它们在同步代码和异步代码之间进行了简单的比较。为了说明(使用Q库):

同步:

var thisReturnsAValue = function() {
  var result = mySynchronousFunction();
  if(result) {
    return getOneValue();
  } else {
    return getAnotherValue();
  }
};

try {
  var value = thisReturnsAValue();
  console.log(value);
} catch(err) {
  console.error(err);
}

异步:

var Q = require('q');

var thisReturnsAPromiseForAValue = function() {
  return Q.Promise(function() {
    return myAsynchronousFunction().then(function(result) {
      if(result) {
        // Even getOneValue() would work here, because a non-promise
        // value is automatically cast to a pre-resolved promise
        return getOneValueAsynchronously();
      } else {
        return getAnotherValueAsynchronously();
      }
    });
  });
};

thisReturnsAPromiseForAValue().then(function(value) {
  console.log(value);
}, function(err) {
  console.error(err);
});

你只需要习惯这样的想法,即返回值总是作为then-callbacks的参数被访问,而链接promises等同于组合函数调用(f(g(h(x))))或以其他方式执行函数({{ 1}})。基本上就是这样!当你介绍分支时,事情变得有点棘手,但你可以从这些第一原则中找出应该做些什么。因为then-callbacks接受promises作为返回值,所以你可以通过返回另一个promise 来异步操作异步操作来改变你异步获得的值,该异步操作根据旧的值解析为新值,而父承诺将直到新的结算才解决!当然,你可以从if-else分支中返回这些承诺。

上面示例中说明的另一个非常好的事情是,promises(至少与Promises / A +兼容)以同样类似的方式处理异常。第一个错误"提出"绕过非错误回调并冒泡到第一个可用的错误回调,就像try-catch块一样。

对于它的价值,我认为尝试使用手工制作的Node.js风格的回调来模仿这种行为,而var x2 = h(x); var x3 = g(x2);库是它自己特殊的地狱:)。

遵循这些准则,您的代码将成为(假设所有函数都是异步并返回promise):

async

答案 1 :(得分:2)

解决方案是@mooiamaduck回答和@Kevin评论的混合。

使用promises,ES6生成器和co库使代码更加清晰。我在阅读postgresql节点库示例(pg)时找到了一个很好的例子。在下面的示例中,pool.connectclient.query是返回Promises的异步操作。我们可以在生成if/else之后轻松添加result,然后进行更多异步操作,使代码看起来像同步。

co(function * () {
  var client = yield pool.connect()
  try {
      yield client.query('BEGIN')
      var result = yield client.query('SELECT $1::text as name', ['foo'])
      yield client.query('INSERT INTO something(name) VALUES($1)', [result.rows[0].name])
      yield client.query('COMMIT')
      client.release()
  } catch(e) {
    // pass truthy value to release to destroy the client
    // instead of returning it to the pool
    // the pool will create a new client next time
    // this will also roll back the transaction within postgres
    client.release(true)
  }
})