避免javascript回调和承诺地狱
我有许多异步方法要执行,我的程序流可以根据每个方法的返回而发生很大的变化。下面的逻辑就是一个例子。我不能用一种简单易读的方式用承诺来写。你会怎么写 Ps:欢迎更复杂的流程 Ps2:is_business是一个预定义的标志,在这里我们可以说是编写业务用户还是个人用户避免javascript回调和承诺地狱,javascript,node.js,promise,Javascript,Node.js,Promise,我有许多异步方法要执行,我的程序流可以根据每个方法的返回而发生很大的变化。下面的逻辑就是一个例子。我不能用一种简单易读的方式用承诺来写。你会怎么写 Ps:欢迎更复杂的流程 Ps2:is_business是一个预定义的标志,在这里我们可以说是编写业务用户还是个人用户 begin transaction update users if updated if is_business update_business if not updated
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);
});
异步:
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);
});
您只需要习惯返回值总是作为参数访问然后回调的想法,并且链接承诺等同于组成函数调用fghx或按顺序var x2=hx执行函数;变量x3=gx2;。基本上就是这样!当您引入分支时,事情会变得有点棘手,但是您可以从这些第一原则中找出要做的事情。因为回调接受承诺作为返回值,所以您可以通过为异步操作返回另一个承诺来改变异步获得的值,该异步操作将在旧值的基础上解析为新值,并且父承诺在新值解析之前不会解析!当然,你也可以从if-else分支内部回报这些承诺
上面示例中说明的另一件非常好的事情是,至少承诺那些符合承诺/A+的人以同样类似的方式处理异常。引发的第一个错误会绕过非错误回调,并冒泡到第一个可用的错误回调,就像try-catch块一样
值得一提的是,我认为尝试使用手工制作的Node.js风格回调和异步库来模拟这种行为是一种特殊的地狱:
遵循这些准则,您的代码将成为假设所有函数都是异步的并返回承诺:
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);
});
承诺的好处在于,它们在同步代码和异步代码之间做了一个简单的类比。要说明如何使用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);
});
异步:
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);
});
您只需要习惯返回值总是作为参数访问然后回调的想法,并且链接承诺等同于组成函数调用fghx或按顺序var x2=hx执行函数;变量x3=gx2;。基本上就是这样!当您引入分支时,事情会变得有点棘手,但是您可以从这些第一原则中找出要做的事情。因为回调接受承诺作为返回值,所以您可以通过为异步操作返回另一个承诺来改变异步获得的值,该异步操作将在旧值的基础上解析为新值,并且父承诺在新值解析之前不会解析!当然,你也可以从if-else分支内部回报这些承诺
上面示例中说明的另一件非常好的事情是,至少承诺那些符合承诺/A+的人以同样类似的方式处理异常。引发的第一个错误会绕过非错误回调,并冒泡到第一个可用的错误回调,就像try-catch块一样
值得一提的是,我认为尝试使用手工制作的Node.js风格回调和异步库来模拟这种行为是一种特殊的地狱:
遵循这些准则,您的代码将成为假设所有函数都是异步的并返回承诺:
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-answer和@Kevin-comment的混合 使用promises、ES6生成器和库可以使代码更加清晰。在阅读postgresql节点库示例时,我发现了一个很好的示例。在下面的示例中,pool.connect和client.query是返回承诺的异步操作。我们可以在获得结果后轻松添加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)
}
})
解决方案是@mooiamaduck-answer和@Kevin-comment的混合 使用promises、ES6生成器和库可以使代码更加清晰。在阅读postgresql节点库示例时,我发现了一个很好的示例。在下面的示例中,pool.connect和client.query是返回承诺的异步操作。我们可以在获得结果后轻松添加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)
}
})
我认为这是一个公平的问题。所有这些操作都是异步的吗?是的@mooiamaduck。实际上,我正在做的数据库插入/更新都是异步的。逻辑有问题。如果是业务{*..*}否则{*删除业务*}。如果不是业务,为什么要删除业务。同样,*更新业务*如果未更新*插入业务*。您应该更新业务或插入业务。您不需要使用任何库。在ES7引入异步和wait之前,您可以实现您的
使用标准ES6 promises/generator实现Promise.coroutine方法,并在生成器函数中处理异步工作流,就像它是同步的一样。如果今天晚些时候我能抽出时间,我将尝试给出一个例子。@Redu理解它的工作原理是好的,但我不确定为什么我会自己实现它,只是说我做到了,当有诸如bluebird's Promise.coroutine或co library这样的选项可用时。我认为这是一个公平的问题。所有这些操作都是异步的吗?是的@mooiamaduck。实际上,我正在做的数据库插入/更新都是异步的。逻辑有问题。如果是业务{*..*}否则{*删除业务*}。如果不是业务,为什么要删除业务。同样,*更新业务*如果未更新*插入业务*。您应该更新业务或插入业务。您不需要使用任何库。在ES7引入relieving async and Wait之前,您可以使用标准ES6 promises/generator实现简单的Promise.corroutine方法,并在generator函数中处理异步工作流,就像它是同步的一样。如果今天晚些时候我能抽出时间,我将尝试给出一个例子。@Redu了解它的工作原理很好,但我不确定为什么我会自己实现它,只是说我做到了,当有诸如bluebird's Promise.coroutine或co library可用的选项时。非常感谢@mooiamaduck。这正是我所期望的。我已经了解它是如何工作的,但不幸的是,在我看来,它仍然很难阅读。我发现创建一个回调函数并在开关/案例中使用标志更容易理解。我觉得这更自然,所以我认为这是一个品味问题。让我害怕的是,非承诺代码需要转换为承诺代码才能与其他承诺代码一起使用。这意味着,一旦你开始使用一些承诺,它们就会淹没你的整个代码库,直到你写的几乎所有东西都是基于承诺的。这并不一定是坏事,但如果你遇到了一条死胡同,代码由于某种原因无法基于承诺,那么你会害怕会发生什么。也许这永远不会发生?非常感谢@mooiamaduck。这正是我所期望的。我已经了解它是如何工作的,但不幸的是,在我看来,它仍然很难阅读。我发现创建一个回调函数并在开关/案例中使用标志更容易理解。我觉得这更自然,所以我认为这是一个品味问题。让我害怕的是,非承诺代码需要转换为承诺代码才能与其他承诺代码一起使用。这意味着,一旦你开始使用一些承诺,它们就会淹没你的整个代码库,直到你写的几乎所有东西都是基于承诺的。这并不一定是坏事,但如果你遇到了一条死胡同,代码由于某种原因无法基于承诺,那么你会害怕会发生什么。也许这永远不会发生?