我对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
承诺或让调用者等待它们来选择控制权将在当前执行上下文中消失的时间点。 Python总是让你没有其他选择,而不是总是await
Future / coroutine,因为否则你将不得不用一个像这样丑陋的包装器函数在循环中展开Future链:
async def unwind(value):
while hasattr(value, '__await__'):
value = await value
return value
所以,问题是:这个决定背后有什么理由吗?为什么Python不允许链式期货?有关于它的讨论吗?有什么方法可以让行为更接近Promise吗?
答案 0 :(得分:5)
让我快速比较 JavaScript的承诺和 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个缩进空间来放大所谓的回调地狱或 pyramid of doom。使用JavaScript的人们多年来都在编写这样的代码!
然后Kris Kowal came with his flaming Q library并通过引入 Promises 的概念保存了失望的JavaScript社区。
该名称故意不是&#34; future&#34;或&#34;任务&#34;。 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
回调中返回的promise,以构建一个干净,透明的链。 (我自己写了这种异步
代码超过一年。)
但是,当您需要某些控制流(如条件分支或循环)时,事情会变得复杂。
当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
(或多或少)。我们快到了。
今天,由于the ES7 specification for async-await非常成熟,任何人都可以使用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中引入协同程序:
def concatNamesById(id1, id2):
return getNameById(id1) + ', ' + getNameById(id2);
等什么?期货在哪里?回调金字塔在哪里?重点是Python人 没有JavaScript人员遇到的任何问题。他们只是使用阻止调用。
那么为什么没有JavaScript人使用阻止调用?因为他们无法做到!好吧,他们想要。相信我。 在他们介绍WebWorkers之前,所有JavaScript代码都在gui线程上运行,并且导致了任何阻塞调用 ui要冻结!这是不可取的,因此编写规范的人会尽一切努力来防止这些事情发生。 截至今天,我知道在浏览器中阻止UI线程的唯一方法是:
async = false
选项目前你无法在JavaScript中实现自旋锁和类似的东西,没有办法。 (直到浏览器供应商开始实施Shared Array Buffers之类的东西,我担心会带来这些东西 一旦发烧友业余爱好者开始使用它们,我们就会感到特别痛苦。)
另一方面,在Python中阻塞调用没有任何问题,因为通常没有这样的东西 &#39; gui thread&#39;。如果你仍然需要一些并行性,你可以开始一个新的线程,并继续努力。这很有用 您希望一次运行多个SOAP请求,但是当您想要利用计算能力时却不那么有用 您的笔记本电脑中的所有cpu核心,因为Global Interpreter Lock将阻止您这样做。 (这是 由multiprocessing模块解决,但这是另一个故事)
那么,为什么Python人需要协程?主要答案是反应式编程现在非常流行。 当然还有其他方面,比如不想为你做的每一个宁静的查询开始一个新线程(有些 众所周知,Python库会泄漏线程ID,直到它们最终崩溃)或者只是想要摆脱所有 不必要的多线程原语,如互斥和信号量。 (我的意思是如果你的原则可以省略那些原语 代码可以重写为协同程序。当你进行真正的多线程时,确实需要它们。)这就是期货的原因 开发了。
Python的期货不允许以任何形式进行链接。它们不打算以这种方式使用。请记住,JavaScript 承诺是将金字塔计划改为一个不错的连锁计划,因此必须放松。 但是自动的 展开需要编写特定的代码,并且需要将来的解决方案来区分所提供的代码 按类型或属性划分的值。也就是说,它会更复杂(===更难调试),并且会是一个 向弱打字迈出一小步,这违背了python的主要原则。 Python的未来很轻巧, 干净,易于理解。他们不需要自动放松。