避免javascript回调和承诺地狱
Avoiding javascript callback and promise hell
我有许多异步方法要执行,我的程序流可能会根据每个方法的返回而发生很大变化。下面的逻辑就是一个例子。我无法使用Promises以一种易于阅读的方式编写它。你会怎么写?
Ps:更复杂的流是受欢迎的。
Ps2: is_business是一个预定义的标志,用来表示我们写的是"业务用户"还是"个人用户"。
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
承诺的好处是它们在同步代码和异步代码之间做了一个简单的类比。为了说明(使用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);
});
你只需要习惯这样的想法,即返回值总是作为回调函数的参数访问,并且链接承诺等同于组合函数调用(f(g(h(x)))
)或按顺序执行函数(var x2 = h(x); var x3 = g(x2);
)。本质上就是这样!当你引入分支时,事情会变得有点棘手,但你可以从这些基本原理中找出该怎么做。因为then回调函数接受promise作为返回值,你可以通过返回另一个promise来改变异步获得的值,异步操作将根据旧值解析为新值,并且直到新值解析后父承诺才会解析!当然,你也可以在if-else分支中返回这些承诺。
在上面的例子中展示的另一个非常好的东西是承诺(至少是那些符合promises/A+的承诺)以同样类似的方式处理异常。"引发"的第一个错误绕过非错误回调,并向上弹出到第一个可用的错误回调,很像try-catch块。
为了它的价值,我认为尝试使用手工制作的node .js风格的回调和async
库来模仿这种行为是它自己的特殊类型的地狱:)。
遵循这些准则,你的代码将变成(假设所有函数都是异步的并且返回承诺):
beginTransaction().then(function() {
// beginTransaction() has run
return updateUsers(); // resolves the boolean value `updated`
}).then(function(updated) {
// updateUsers() has "returned" `updated`
if(updated) {
if(isBusiness) {
return updateBusiness().then(function(updated) {
if(!updated) {
return insertBusiness();
}
// It's okay if we don't return anything -- it will
// result in a promise which immediately resolves to
// `undefined`, which is a no-op, just like a missing
// else-branch
});
} else {
return deleteBusiness();
}
} else {
if(upsert) {
return insertUser().then(function() {
if(isBusiness) {
return insertBusiness();
}
});
}
}
}).then(function() {
return commitTransaction();
}).done(function() {
console.log('all done!');
}, function(err) {
console.error(err);
});
解决方案是@mooiamaduck答案和@Kevin评论的组合。
使用promises、ES6生成器和co库使代码更加清晰。我发现一个很好的例子,当阅读postgresql节点库的例子(pg)。在下面的示例中,pool.connect
和client.query
是返回promise的异步操作。我们可以在获得result
之后轻松地添加if/else
,然后进行更多的异步操作,使代码看起来像同步。
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)
}
})
- 我的职位回报太快了,如何做出承诺
- 打破承诺链的好方法是什么
- 从函数返回角度承诺
- 我怎样才能获得承诺的价值
- 延期承诺值未更新/解析/延期
- 在承诺链中处理早期回报的最佳方式
- 承诺在非节点式回调上使用Bluebird
- 简单的ES6承诺问题-交换解决和拒绝参数
- 组合承诺和非承诺值
- 带有对象/原型的链式承诺(Q延期)
- AngularJS$q承诺使用socket.io
- React JS:未捕获(在承诺中)语法错误:在位置 0 的 JSON 中意外<令牌
- 欢迎来到承诺(d)地狱;-)
- 我如何使用承诺重构这个回调地狱
- 包装现有的API'在承诺避免“;回调地狱”;
- 回调地狱与承诺
- 我如何运行async承诺没有承诺地狱
- 避免回调地狱.流星.叫承诺
- 如何将回调地狱重写为承诺
- 避免javascript回调和承诺地狱