MadelineProtoDocs/docs/docs/ASYNC.md

8.6 KiB

title description image
Async MadelineProto now features async, for **incredible speed improvements**, and parallel processing **without** buggy and slow threading/multiprocessing. https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png

Async

MadelineProto now features async, for incredible speed improvements, and parallel processing without buggy and slow threading/multiprocessing.
Powered by amphp, MadelineProto wraps the AMPHP APIs to provide a simpler generator-based async API.

Usage

What exactly is async, you may ask, and how is it better than threading or multiprocessing?
Async is a relatively new programming pattern that allows you to easily write non-blocking code as if you were using standard blocking functions, all without the need for complex message exchange systems and synchronization handling for threaded programs, that only add overhead and complexity to your programs, making everything slower and error-prone.
That's very cool and all, you might think, but how exactly does this async stuff work? Well, as it turns out, it's very easy.

Instead of writing code like this:

$file = $MadelineProto->download_to_dir($bigfile, '/tmp/');

Write it like this:

$file = yield $MadelineProto->download_to_dir($bigfile, '/tmp/');

That's it.

It's really that easy, you just have to add a yield before calling MadelineProto methods.
The yield will automatically suspend the execution of the function, letting the program do other stuff while the file is being downloaded.
Once the file is downloaded, execution is automatically resumed from that exact point in the function.

Loading the latest version of MadelineProto

In order to use the yield operator in MadelineProto, you have to load the latest version of MadelineProto from the master branch (alpha) by loading it through composer (dev-master) or with madeline.php:

<?php

if (!file_exists('madeline.php')) {
    copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
define('MADELINE_BRANCH', '');
include 'madeline.php';

The MADELINE_BRANCH constant you defines which branch of MadelineProto madeline.php should load.
When the constant is not set, the old stable branch is loaded; if the value is an empty string, the master branch is loaded; otherwise, the selected branch name is loaded.

Enabling the MadelineProto async API

The yield operator can only be used within functions, once MadelineProto's async mode is enabled.
To do that, simply run the async function, or pass the async enabler flag separately to each method call, if you want to make only some calls async.

This will enable async mode for all MadelineProto functions:

$MadelineProto->async(true);
// ...
yield $MadelineProto->messages->sendMessage(...);

This will enable async mode for only for one specific call of a MadelineProto function (by adding a new array parameter after all the required parameters):

yield $MadelineProto->messages->sendMessage(..., ['async' => true]);

Using the MadelineProto async API

As mentioned earlier, you can only use the yield operator within functions, but not just any function, for example:

$sm = function ($chatID, $message) use ($MadelineProto) {
    $id = (yield $MadelineProto->get_info($chatID))['bot_api_id'];
    $res = yield $MadelineProto->messages->sendMesssage(['peer' => $chatID, 'message' => "Message from: $id\n$message"], ['async' => true]);
    return $res;
};
$result = $sm('@danogentili', 'hi');

This will not work, because the result of a function that uses yield is not the returned value, but a generator, which is what the async AMPHP API is based on.
If the generator is not passed to the AMPHP event loop, execution of the function will not be resumed: when MadelineProto asynchronously obtains the result of the get_info, execution of the function is never resumed, and the line with sendMessage is never called.
To avoid this problem, only call asynchronous functions in the event/callback update handler, or in functions called by the event/callback update handler, or inside a function passed to loop.

Async in event handler:

$MadelineProto->async(true);
class EventHandler extends \danog\MadelineProto\EventHandler
{
    public function onAny($update)
    {
        if (isset($update['message']['out']) && $update['message']['out']) {
            return;
        }
        if (isset($update['message']['media']) && $update['message']['media']['_'] !== 'messageMediaGame') {
            yield $this->download_to_dir($update, '/tmp');
            yield $this->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
        }

        $res = json_encode($update, JSON_PRETTY_PRINT);

        yield $this->sleep(3);

        try {
            yield $this->sm($update, "<code>$res</code>\nAsynchronously, after 3 seconds");
        } catch (\danog\MadelineProto\RPCErrorException $e) {
            \danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
        } catch (\danog\MadelineProto\Exception $e) {
            \danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
        }
    }
    public function sm($peer, $message)
    {
        yield $this->messages->sendMessage(['peer' => $peer, 'message' => $message, 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']);
    }
}

Async in callback handler:

$MadelineProto->async(true);
$MadelineProto->setCallback(function ($update) use ($MadelineProto) {
    if (isset($update['message']['out']) && $update['message']['out']) {
        return;
    }
    yield $MadelineProto->sleep(3);
    yield $MadelineProto->messages->sendMessage(['peer' => $update, 'message' => 'Hi after 3 seconds']);
});

Wrapped async

$MadelineProto->async(true);
$MadelineProto->loop(function () use ($MadelineProto) {
    yield $MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'hi']);
    // You can also have an asynchronous get_updates (deprecated) loop in here, if you want to; just don't forget to use yield for all MadelineProto functions.
});

Ignored async

$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'a'], ['async' => true]);
$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'b'], ['async' => true]);

You can use the async version of MadelineProto functions without yield if you don't want the request to block, and you don't need the result of the function.
This is allowed, but the order of the function calls will not be guaranteed: you can use call queues if you want to make sure the order of the calls remains the same.

MadelineProto and AMPHP async APIs

MadelineProto and AMPHP both provide a lot of async functions: all of MadelineProto's functions are async, for example; and AMPHP provides multiple packages to work asynchronously with HTTP requests, websockets, databases (MySQL, redis, postgres, DNS, sockets and much more!
When using AMPHP libraries, you just have to use them with yield, no need to start the event loop, as long as you're running the functions inside MadelineProto's update handler/loop.

Also, you should read the AMPHP docs, especially the event loop docs: AMPHP provides multiple helper methods for executing actions repeatedly every N seconds in a non-blocking manner, or to defer execution of certain actions (aka async cron).

MadelineProto also provides a few generic async helper methods:

$MadelineProto->sleep(3); // Async sleep

Next section