Search code examples
javascriptpythonpython-3.xpromisepython-asyncio

How do I write a sequence of promises in Python?


Is it possible to write a sequence of promise (or tasks) using only Python 3.6.1 Standard Library?

For example, a sequence promises in JavaScript is written as:

const SLEEP_INTERVAL_IN_MILLISECONDS = 200;

const alpha = function alpha (number) {
    return new Promise(function (resolve, reject) {
        const fulfill = function() {
            return resolve(number + 1);
        };

        return setTimeout(fulfill, SLEEP_INTERVAL_IN_MILLISECONDS);
    });
};

const bravo = function bravo (number) {
    return new Promise(function (resolve, reject) {
        const fulfill = function() {
            return resolve(Math.ceil(1000*Math.random()) + number);
        };
        return setTimeout(fulfill, SLEEP_INTERVAL_IN_MILLISECONDS);
    });
};

const charlie = function charlie (number) {
    return new Promise(function (resolve, reject) {
        return (number%2 == 0) ? reject(number) : resolve(number);
    });
};

function run() {
    return Promise.resolve(42)
        .then(alpha)
        .then(bravo)
        .then(charlie)
        .then((number) => {
            console.log('success: ' + number)
        })
        .catch((error) => {
            console.log('error: ' + error);
        });
}

run();

Each function also returns a Promise with asynchronous processing result, that would be resolved/rejected by the immediately following promise.

I am aware of libraries such as promises-2.01b and asyncio 3.4.3 and I am looking for a Python STL solution. Thus, if I need to import a non-STL library, I prefer using RxPython instead.


Solution

  • Here's a similar program using asyncio and the async/await syntax:

    import asyncio
    import random
    
    async def alpha(x):
        await asyncio.sleep(0.2)
        return x + 1 
    
    async def bravo(x):
        await asyncio.sleep(0.2)
        return random.randint(0, 1000) + x
    
    async def charlie(x):
        if x % 2 == 0:
            return x
        raise ValueError(x, 'is odd')
    
    async def main():
        try:
            number = await charlie(await bravo(await alpha(42)))
        except ValueError as exc:
            print('error:', exc.args[0])
        else:
            print('success:', number)
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    EDIT: If you're interested in reactive streams, you might consider using aiostream.

    Here's a simple example:

    import asyncio
    from aiostream import stream, pipe
    
    async def main():
        # This stream computes 11² + 13² in 1.5 second
        xs = (
            stream.count(interval=0.1)      # Count from zero every 0.1 s
            | pipe.skip(10)                 # Skip the first 10 numbers
            | pipe.take(5)                  # Take the following 5
            | pipe.filter(lambda x: x % 2)  # Keep odd numbers
            | pipe.map(lambda x: x ** 2)    # Square the results
            | pipe.accumulate()             # Add the numbers together
        )
        print('11² + 13² = ', await xs)
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    More examples in the documentation.

    Disclaimer: I am the project maintainer.