Difference in the ES6 Promises and PEP3148 Futures Chain

I am somewhat puzzled by the discussion of differences in the implementation of ES6 Promises and PEP3148 futures. In Javascript, when a promise is resolved with another promise, the “outer” promise inherits the value of the “inner” promise after it is resolved or rejected. In Python, the “external” future is instead immediately resolved with the “internal” future, and not with it ultimately, and this is a problem.

To illustrate this, I have provided two code snippets for both platforms. In Python, the code is as follows:

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()) 

In Javascript, the code is completely equivalent:

 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 provided for completeness.

Javascript code prints 42 , as expected. Python code prints <coroutine object foo at 0x102a05678> , and complaints about the "coroutine" foo were never expected.

In this way, JS allows you to select the point when the control is removed from your current execution context, immediately await ing promises or let the caller wait for them. Python literally leaves you no choice but to always await Future / coroutine, because otherwise you will have to unwind the Future chain in a loop yourself with an ugly wrapper function like this:

 async def unwind(value): while hasattr(value, '__await__'): value = await value return value 

So the question is: Are there any arguments in favor of this decision? Why does Python forbid chained futures? Have there been any discussions about this? Is there anything that can be done to bring Promises behavior closer?

+5
source share
1 answer

Let me show you a quick comparison of JavaScript Promises and Python futures, where I can point out the main features and show the reasons for the solutions.

I will use the following dummy example to demonstrate the use of asynchronous functions:

 async function concatNamesById(id1, id2) { return (await getNameById(id1)) + ', ' + (await getNameById(id2)); } 

Asynchronous JavaScript

On the same day, before the advent of the Promises concept, people wrote down their code using callbacks. There are various conventions as to what argument the callback should be, how to handle errors, etc. At the end, our function looks something like this:

 // 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); } }); } }); } 

This does the same as in the example, and yes, I used 4 spaces indented intentionally to increase the problem with the so-called hell callback or death pyramid . People using JavaScript have been writing code for many years!

Chris Koval then came with his flaming Q library and saved the frustrated JavaScript community by introducing the concept of Promises. The name is intentionally not "future" or "task." The main goal of the Promise concept is to get rid of the pyramid. To achieve this, promises have a then method, which not only allows you to subscribe to an event that will be fired when the promised value is received, but will also return another promise, allowing the chain. This is what makes Promises and Futures another concept. Promises a little more.

 // 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 } 

Cm? You must deploy the Promises returned from then callbacks to create a clean, transparent chain. (I myself wrote such asynchronous code for more than a year.) However, it gets complicated when you need some control flow, for example, conditional branches or loops. When the first compilers / transpilers (like 6to5) for ES6 appeared, people slowly started using generators. ES6 generators are two directional, which means the generator not only produces values, but can also receive setpoints at each iteration . This allowed us to write the following code:

 // using generators and promises const concatNamesById = Q.async(function*(id1, id2) { return (yield getNameById(id1)) + ', ' + (yield getNameById(id2)); }); 

Still using Promises, Q.async makes an asynchronous function from the generator. There is no black magic, this wrapper function is implemented using nothing but promise.then (more or less). We are almost there.

Today, since the ES7 specification for asynchronous wait is pretty mature, anyone can compile asynchronous ES7 code for ES5 using BabelJS .

 // using real async-await async function concatNamesById(id1, id2) { return (await getNameById(id1)) + ', ' + (await getNameById(id2)); } 

Return promise from an asynchronous function

So this works:

 async foo() { return /* await */ sleep('bar', 1000); // No await is needed! } 

so:

 async foo() { return await await await 'bar'; // You can write await pretty much everywhere you want! } 

This type of weak / dynamic / duck typing is well suited for viewing in the world around the world.

You are right that you can return a promise from an asynchronous function without waiting, and it breaks. This is not a solution, but a direct consequence of how promise.then works, as it promises to make the chain convenient. However, I think it is good practice to write a wait before each asynchronous call, to make sure that you know that the call is asynchronous. . We have several errors every day due to the lack of pending keywords, because they will not cause instant errors, just a bunch of random tasks running in parallel. I like to debug them. Really.

Asynchronous Python

See what Python people did before asynchronous expectations coroutines were added in python:

 def concatNamesById(id1, id2): return getNameById(id1) + ', ' + getNameById(id2); 

Wait what? Where are the futures? Where is the callback pyramid? The thing is, Python people do not have the problems people have encountered in JavaScript. They just use blocking calls.

Big difference between JavaScript and Python

So why didn't JavaScript users use blocking calls? Because they could not! Well, they wanted to. Believe me. Before they introduced WebWorkers , all of the JavaScript code worked in the gui thread, and any blocking call caused ui to freeze! This is undesirable, so the people who wrote the specifications did everything to prevent such things. Today, the only way I can block the user interface flow in a browser that I know of:

  • Using XMLHttpRequest with deprecated async = false option
  • Using Spin Waiting
  • (Or do real heavy calculations)

Currently, you cannot implement spinlocks and similar things in JavaScript, there is simply no way. (Until browser developers start implementing things like Shared Array Buffers , which I'm afraid will bring a special kind of hell as soon as enthusiast enthusiasts start using them)

On the other hand, there is nothing wrong with blocking calls in Python, since there is usually no such thing as a 'gui thread'. If you still need a little parallelism, you can start a new thread and work on it. This is useful when you want to run multiple SOAP requests at once, but not so useful when you want to use the processing power of all the processor cores in your laptop, as Global Interpreter Lock will prevent you from doing this. (This was worked on multiprocessing , but that's a different story)

So why do Python people need coroutines? The main answer is that reactive programming has become really popular these days. Of course, there are other aspects, such as the reluctance to start a new thread for each of your quiet requests (some Python libraries are known to skip thread IDs until they ultimately fail) or just want to get rid of all unnecessary multi-threaded primitives, such as mutexes and semaphores. (I mean, these primitives can be omitted if the code can be rewritten into coroutines. Of course, they are necessary when you are doing real multithreading.) And that is why futures were developed.

Python futures do not allow chaining in any form. They are not intended to be used in this way. Remember, JavaScript promises had to change the pyramid scheme to a beautiful chain scheme, so you had to unwind it. But automatic for unwinding requires specific code for writing and needs a future resolution in order to distinguish between the set values ​​according to their types or properties. That is, it would be more complex (more difficult to debug) and would be a small step towards weak typing, which contradicts the basic principles of python. Python futures are easy, clean and straightforward. They just do not need automatic unwinding.

+5
source

Source: https://habr.com/ru/post/1236191/


All Articles