[英]Chaining difference in ES6 Promises and PEP3148 Futures
我对ES6 Promises和PEP3148期货的实施差异进行推理有点困惑。 在Javascript中,当Promise与另一个Promise一起解决时,“outer”promise会在解决或拒绝后继承“内部”promise的值。 在Python中,“外部”的未来会立即用“内在的”未来来解决,而不是它的最终价值,这就是问题所在。
为了说明这一点,我为两个平台提供了两个代码片段。 在Python中,代码如下所示:
import asyncio
async def foo():
return asyncio.sleep(delay=2, result=42)
async def bar():
return foo()
async def main():
print(await bar())
asyncio.get_event_loop().run_until_complete(main())
在Javascript中,完全等效的代码是这样的:
function sleep(delay, result) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result);
}, delay * 1000);
});
}
async function foo() {
return sleep(2, 42);
}
async function bar() {
return foo();
}
(async function main() {
console.log(await bar());
})();
提供sleep
功能是为了完整。
正如预期的那样,Javascript代码打印42
。 Python代码打印<coroutine object foo at 0x102a05678>
并且从未等待过关于“coroutine'foo'的投诉”。
这样,JS允许您通过立即await
promise或者让调用者等待它们来选择控制将在当前执行上下文中消失的时间点。 Python总是让你没有别的选择,总是await
Future / coroutine,因为否则你将不得不用一个丑陋的包装器函数在循环中展开Future链,如下所示:
async def unwind(value):
while hasattr(value, '__await__'):
value = await value
return value
所以,问题是 : 这个决定背后有什么理由吗? 为什么Python不允许链式期货? 有关于它的讨论吗? 是否可以采取任何措施使行为更接近Promise?
让我展示一下JavaScript的Promises和Python的未来的快速比较,我可以在其中指出主要的用例并揭示决策背后的原因。
我将使用以下虚拟示例来演示异步函数的使用:
async function concatNamesById(id1, id2) {
return (await getNameById(id1)) + ', ' + (await getNameById(id2));
}
回到白天,在Promises的概念出现之前,人们使用回调编写了他们的代码。 关于哪个参数应该是回调,如何处理错误等等仍然存在各种约定......最后,我们的函数看起来像这样:
// using callbacks
function concatNamesById(id1, id2, callback) {
getNameById(id1, function(err, name1) {
if (err) {
callback(err);
} else {
getNameById(id2, function(err, name2) {
if (err) {
callback(err);
} else {
callback(null, name1 + ', ' + name2);
}
});
}
});
}
这与示例相同,是的,我有意使用4个缩进空间来放大所谓的回调地狱或厄运金字塔的问题 。 使用JavaScript的人们多年来都在编写这样的代码!
然后, Kris Kowal带着他火红的Q库 ,通过引入Promises的概念来拯救失望的JavaScript社区。 该名称故意不是“未来”或“任务”。 Promise概念的主要目标是摆脱金字塔。 为了实现这个承诺,有一个then
方法,它不仅允许您订阅在获得承诺值时触发的事件,而且还将返回另一个承诺,允许链接 。 这就是使Promises和future成为一个不同概念的原因。 承诺更多一点。
// using chained promises
function concatNamesById(id1, id2) {
var name1;
return getNameById(id1).then(function(temp) {
name1 = temp;
return getNameById(id2); // Here we return a promise from 'then'
}) // this then returns a new promise, resolving to 'getNameById(id2)', allows chaining
.then(function(name2) {
return name1 + ', ' + name2; // Here we return an immediate value from then
}); // the final then also returns a promise, which is ultimately returned
}
看到? 必须从then
回调中解包返回的promises,以构建一个干净,透明的链。 (我自己写了这种异步代码超过一年。)然而,当你需要像条件分支或循环这样的控制流程时,事情会变得复杂。 当ES6的第一个编译器/转换器(如6to5)出现时,人们慢慢开始使用发电机。 ES6生成器是双向的,这意味着生成器不仅生成值,而且可以在每次迭代时接收提供的值 。 这允许我们编写以下代码:
// using generators and promises
const concatNamesById = Q.async(function*(id1, id2) {
return (yield getNameById(id1)) + ', ' + (yield getNameById(id2));
});
仍然使用promises, Q.async
从生成器生成异步函数。 那里没有黑魔法,这个包装函数只使用promise.then
(或多或少)实现。 我们快到了。
今天,由于async-await的ES7规范已经相当成熟,任何人都可以使用BabelJS将异步ES7代码编译为ES5。
// using real async-await
async function concatNamesById(id1, id2) {
return (await getNameById(id1)) + ', ' + (await getNameById(id2));
}
这样可行:
async foo() {
return /* await */ sleep('bar', 1000);
// No await is needed!
}
这样做:
async foo() {
return await await await 'bar';
// You can write await pretty much everywhere you want!
}
这种弱/动态/鸭子打字非常适合JavaScript的世界观。
你是对的,你可以在没有等待的情况下从异步函数返回一个promise,它就会被解除。 这是不是一个真正的决定,但是是怎么造成的直接后果promise.then
的作品,因为它解开有望使链接舒服。 尽管如此,我认为在每次异步调用之前编写等待是一个很好的做法,以明确你知道调用是异步的 。 由于缺少等待关键字,我们每天都有多个错误,因为它们不会导致瞬时错误,只是一堆并行运行的随机任务。 我喜欢调试它们。 认真。
让我们看看Python人员在异步之前做了什么 - 等待在python中引入了coroutines:
def concatNamesById(id1, id2):
return getNameById(id1) + ', ' + getNameById(id2);
等什么? 期货在哪里? 回调金字塔在哪里? 重点是Python人员没有JavaScript人员遇到的任何问题。 他们只是使用阻止调用。
那么为什么JavaScript人们不使用阻止调用? 因为他们不能! 好吧,他们想要。 相信我。 在他们介绍WebWorkers之前,所有JavaScript代码都在gui线程上运行,并且任何阻塞调用都会导致ui冻结! 这是不可取的,因此编写规范的人会尽一切努力来防止这些事情发生。 截至今天,我知道在浏览器中阻止UI线程的唯一方法:
async = false
选项一起使用 目前你无法在JavaScript中实现自旋锁和类似的东西,没有办法。 (直到浏览器供应商开始实施共享数组缓冲区之类的东西,我担心,一旦发烧友业余爱好者开始使用它们,我们就会带来一种特殊的地狱般的痛苦)
另一方面,在Python中阻塞调用没有任何问题,因为通常没有'gui线程'这样的东西。 如果你仍然需要一些并行性,你可以开始一个新的线程,并继续努力。 当您想要一次运行多个SOAP请求时,这非常有用,但是当您想要利用笔记本电脑中所有CPU内核的计算能力时,这一点非常有用,因为Global Interpreter Lock会阻止您这样做。 (这是由多处理模块解决的,但这是另一个故事)
那么,为什么Python人需要协程? 主要答案是反应式编程现在非常流行。 当然还有其他方面,比如不想为你做的每一个宁静的查询启动一个新的线程,(一些Python库已知会泄漏线程ID,直到它们最终崩溃)或者只是想要摆脱所有不必要的多线程原语,如互斥和信号量。 (我的意思是如果您的代码可以重写为协同程序,那么这些原语可以省略。当您进行真正的多线程时,确实需要它们。)这就是为什么期货被开发出来的原因。
Python的未来不允许任何形式的链接。 它们不打算以这种方式使用。 请记住,JavaScript的承诺是将金字塔方案更改为一个很好的链式方案,因此需要展开。 但是自动展开需要编写特定的代码,并且需要将来的解决方案来区分提供的类型或属性。 也就是说,它会更复杂(===更难调试),并且会向弱打字迈出一小步,这违背了python的主要原则。 Python的未来是轻量级,干净且易于理解的。 他们只是不需要自动展开。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.