ES6 Promises和PEP3148期货的链接差异
我对ES6 Promises和PEP3148期货执行差异的推理有点困惑。 在Javascript中,当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 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不允许链式期货? 有没有关于它的讨论? 有什么可以做,使行为匹配承诺接近?
让我展示JavaScript的Promises和Python的未来的快速比较,在这里我可以指出主要的用例并揭示决策背后的原因。
我将使用下面的虚拟示例来演示如何使用异步函数:
async function concatNamesById(id1, id2) {
return (await getNameById(id1)) + ', ' + (await getNameById(id2));
}
异步JavaScript
早在Promise的概念出现之前,人们就使用回调来编写代码。 关于哪个参数应该是回调函数,应该如何处理错误等,还有各种约定......最后,我们的函数看起来像这样:
// 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
方法,它不仅允许你订阅在获得承诺值时触发的事件,而且还会返回另一个承诺,允许链接。 这就是使承诺和期货成为不同概念的原因。 承诺是多一点。
// 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
回调建设廉洁,透明的链条。 (我自己写了这种异步代码超过一年。)然而,当你需要一些控制流如条件分支或循环时,情况会变得复杂。 当ES6的第一个编译器/转译器(如6to5)出现时,人们开始慢慢地开始使用发生器。 ES6发生器是双向的,这意味着发生器不仅产生值,而且可以在每次迭代中接收提供的值 。 这使我们能够编写下面的代码:
// using generators and promises
const concatNamesById = Q.async(function*(id1, id2) {
return (yield getNameById(id1)) + ', ' + (yield getNameById(id2));
});
仍然使用承诺, Q.async
从一个生成器中产生一个异步函数。 这里没有黑魔法,这个包装函数只用promise.then
(或多或少)来实现。 我们快到了。
今天,由于异步等待的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.then
如何运作,因为它解开了让链接舒适的承诺。 不过,我认为在每次异步调用之前编写await是一个很好的做法,以清楚地表明您知道该调用是异步的 。 由于缺少关键字,我们每天都有多个错误,因为它们不会造成即时错误,只是一堆随机并行运行的任务。 我喜欢调试它们。 认真。
异步Python
让我们来看看Python人在python中引入了异步等待协程之前做了什么:
def concatNamesById(id1, id2):
return getNameById(id1) + ', ' + getNameById(id2);
等什么? 期货在哪里? 回调金字塔在哪里? 关键是Python用户没有任何JavaScript人员遇到的问题。 他们只是使用阻止呼叫。
JavaScript和Python之间的巨大差异
那么为什么JavaScript用户不使用阻塞呼叫? 因为他们不能! 那么,他们想要。 相信我。 在他们介绍WebWorkers之前,所有JavaScript代码都在gui线程上运行,并且任何阻塞通话都会导致ui冻结! 这是不可取的,因此编写规范的人会尽一切努力来防止这种情况。 截至今天,我知道阻止浏览器UI线程的唯一方法:
async = false
选项一起使用 目前,你不能在JavaScript中实现旋转锁和类似的东西,但是没有办法。 (在浏览器供应商开始实施共享阵列缓冲区之类的事情之前,只要有爱好者的业余爱好者开始使用它们,恐怕会给我们带来一种特殊的困扰)
另一方面,在Python中阻塞调用没有任何问题,因为通常不存在'gui线程'这样的事情。 如果你仍然需要一些并行性,你可以开始一个新的线程,并开展工作。 当您想要一次运行多个SOAP请求时,这非常有用,但当您想要利用笔记本电脑中所有cpu核心的计算能力时,这一点非常有用,因为Global Interpreter Lock会阻止您这样做。 (这是由多处理模块解决的,但这是另一个故事)
那么,为什么Python人需要协程呢? 主要答案是反应式编程现在非常流行。 当然还有其他的方面,比如不想为你所做的每一个平静的查询启动一个新线程(一些Python库已知会泄漏线程ID直到它们最终崩溃),或者只是想摆脱所有不必要的多线程基元互斥体和信号量。 (我的意思是,如果你的代码可以被重写成协程,那么这些原语可以被省略,当你真正实现多线程时,确实需要它们。)这就是为什么期货被开发出来的原因。
Python的未来不允许任何形式的链接。 他们不打算以这种方式使用。 请记住,JavaScript的承诺是将金字塔计划改为一个不错的连锁计划,因此解除合同是必要的。 但是自动展开需要编写特定的代码,并且需要将来的分辨率根据类型或属性区分提供的值。 也就是说,它会更复杂(===更难调试),并且会对弱打字稍微迈进一步,这与python的主要原则背道而驰。 Python的未来是轻量级的,干净和容易理解的。 他们不需要自动解开。
链接地址: http://www.djcxy.com/p/55429.html上一篇: Chaining difference in ES6 Promises and PEP3148 Futures
下一篇: Questions set as resource to learn programming futures, promises and actors