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线程的唯一方法:

  • 将XMLHttpRequest与不建议使用的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