Python3 中的 Future 和 ES6 中的 Promises 的区别

Differences between Futures in Python3 and Promises in ES6

自 Python 3.5 起,关键字 awaitasync 被引入该语言。现在,我更像是一个 Python 2.7 的人,我已经避免 Python 3 很长一段时间了,所以 asyncio 对我来说很新。根据我的理解,await/async 的工作方式似乎与它们在 ES6 中的工作方式非常相似(或 JavaScript、ES2015,但是你想怎么称呼它。)

这是我制作的两个脚本来比较它们。

import asyncio

async def countdown(n):
    while n > 0:
        print(n)
        n -= 1
        await asyncio.sleep(1)

async def main():
    """Main, executed in an event loop"""

    # Creates two countdowns
    futures = asyncio.gather(
        countdown(3), 
        countdown(2)
    )
    # Wait for all of them to finish
    await futures
    # Exit the app
    loop.stop()

loop = asyncio.get_event_loop()
asyncio.ensure_future(main())
loop.run_forever()
function sleep(n){
    // ES6 does not provide native sleep method with promise support
    return new Promise(res => setTimeout(res, n * 1000));
}

async function countdown(n){
    while(n > 0){
        console.log(n);
        n -= 1;
        await sleep(1);
    }
}

async function main(){
    // Creates two promises
    var promises = Promise.all([
        countdown(3),
        countdown(2)
    ]);
    // Wait for all of them to finish
    await promises;
    // Cannot stop interpreter's event loop
}

main();

需要注意的一件事是这些代码非常相似,而且它们的工作方式几乎相同。

问题如下:

  1. 在Python和ES6中,await/async都是基于生成器的。认为 Futures 与 Promises 相同是正确的吗?

  2. 我在 asyncio 文档中看到了术语 TaskFutureCoroutine。它们有什么区别?

  3. 我是否应该开始编写 Python 始终具有事件循环的代码 运行?

  1. In both Python and ES6, await/async are based on generators. Is it a correct to think Futures are the same as Promises?

不是Future,但Python的Task大致相当于Javascript的Promise。请参阅下面的更多详细信息。

  1. I have seen the terms Task, Future and Coroutine used in the asyncio documentation. What are the differences between them?

它们是完全不同的概念。 Task主要由FutureCoroutine组成。让我们简要描述一下这些原语(我将简化很多事情以仅描述主要原理):

未来

Future 只是对值的抽象,可能尚未计算但最终会可用。这是一个只做一件事的简单容器——每当设置值时,触发所有已注册的回调。

如果你想获得那个值,你通过add_done_callback()方法注册一个回调。

但与 Promise 不同的是,实际计算是在外部完成的 - 外部代码必须调用 set_result() 方法来解析未来。

协程

协程是与Generator非常相似的对象。

生成器通常在 for 循环中迭代。它 产生 值,并且从 PEP342 接受开始,它 接收 值。

协程通常在 asyncio 库深处的事件循环中迭代。协程产生 Future 个实例。当你迭代一个协程并产生一个未来时,你应该等到这个未来被解决。之后你应该 send 未来的价值进入协程,然后你收到另一个未来,依此类推。

await 表达式实际上与 yield from 表达式相同,因此通过 等待 其他协程,您将停止,直到该协程的所有未来都已解决,并获取协程的 return 值。 Future 是一次迭代,它的迭代器 return 是实际的 Future - 这大致意味着 await future 等于 yield from future 等于 yield future

任务

Task是实际开始计算的Future,附加到事件循环中。所以它是一种特殊的 Future(class Task 派生自 class Future),它与一些事件循环相关联,并且它有一些协程,用作 任务执行者.

任务通常由事件循环对象创建:您为循环提供协程,它创建任务对象并开始以上述方式迭代该协程。协程完成后,Task 的 Future 由协程的 return 值解决。

你看,任务与 JS Promise 非常相似 - 它封装了后台作业及其结果。

协程函数和异步函数

Coroutine func 是一个协程工厂,就像生成器函数之于生成器。注意 Python 的协程函数和 Javascript 的异步函数之间的区别 - JS 异步函数在被调用时会创建一个 Promise 并且其内部生成器会立即开始迭代,而 Python' s 协程什么都不做,直到在其上创建任务。

  1. Should I start writing Python code that always has an event loop running?

如果您需要任何 asyncio 功能,那么您应该这样做。事实证明,很难混合使用同步和异步代码——你的整个程序最好是异步的(但你可以通过 asyncio 线程池 API)

在单独的线程中启动同步代码块

我看到了下游的主要区别。

const promise = new Promise((resolve, reject) => sendRequest(resolve, reject));
await promise;

在JavaScript中,resolvereject 两个函数是由JS 引擎创建的,它们必须传递给您以便您跟踪它们。最后,大部分时间您仍在使用两个回调函数,并且 Promise 在 doStuff 调用 resolve 之后实际上不会做超过 setTimeout(() => doMoreStuff()) 的事情。一旦调用回调,就无法检索旧结果或 Promise 的状态。 Promise 主要只是常规调用和 async/await 之间的胶水代码(因此您可以在其他地方等待 promise)和一些用于链接 .thens.

的错误回调转发
future = asyncio.Future()
sendRequest(future)
await future

在 Python 中,Future 本身成为返回结果的接口,并跟踪结果。

由于 Andril 给出了最接近 Python 等价于 JavaScript 的 Promise(即 Task;你给它一个回调并等待它完成),我想去另一种方式。

class Future {
  constructor() {
    this.result = undefined;
    this.exception = undefined;
    this.done = false;
    this.success = () => {};
    this.fail = () => {};
  }
  result() {
    if (!this.done) {
      throw Error("still pending");
    }
    return this.result();
  }
  exception() {
    if (!this.done) {
      throw Error("still pending");
    }
    return this.exception();
  }
  setResult(result) {
    if (this.done) {
      throw Error("Already done");
    }
    this.result = result;
    this.done = true;
    
    this.success(this.result);
  }
  setException(exception) {
    if (this.done) {
      throw Error("Already done");
    }
    this.exception = exception;
    this.done = true;

    this.fail(this.exception);
  }
  then(success, fail) {
    this.success = success;
    this.fail = fail;
  }
}

JS await 基本上生成两个传递给 .then 的回调,其中在 JS Promise 中应该发生实际逻辑。在许多示例中,您会在此处找到 setTimeout(resolve, 10000) 来演示跳出事件循环,但如果您改为跟踪这两个回调,则可以随心所欲地使用它们..

function doStuff(f) {
  // keep for some network traffic or so
  setTimeout(() => f.setResult(true), 3000);
}

const future = new Future();
doStuff(future);
console.log('still here');
console.log(await future);

上面的例子证明了; 'still here' 三秒后你得到 'true'.

如你所见,不同之处在于 Promise 接收一个工作函数并在内部处理 resolve 和 reject,而 Future 不将工作内部化,只关心回调。就个人而言,我更喜欢 Future,因为它少了一层回调地狱——这是 Promises 的首要原因之一:回调链。