Skip to content

Async Command Execution

New in v2.0

Async command execution is a new feature in Yalla CLI v2.0 that allows you to run commands asynchronously with promises and parallel execution support.

Overview

The SupportsAsync trait enables commands to run asynchronously, allowing you to execute long-running tasks without blocking the main thread. This is particularly useful for commands that need to process multiple operations in parallel or handle time-consuming tasks.

Basic Usage

Enabling Async Support

Add the SupportsAsync trait to your command class:

php
<?php

use Yalla\Commands\Command;
use Yalla\Commands\Traits\SupportsAsync;
use Yalla\Output\Output;

class ProcessCommand extends Command
{
    use SupportsAsync;

    protected bool $runAsync = true; // Enable async by default
    protected int $asyncTimeout = 300; // 5 minutes timeout

    public function __construct()
    {
        $this->name = 'process';
        $this->description = 'Process data asynchronously';
    }

    public function execute(array $input, Output $output): int
    {
        $output->info('Processing data...');

        // Long running task
        sleep(5);

        $output->success('Processing complete!');

        return 0;
    }
}

Running Commands Asynchronously

Users can run commands asynchronously using the --async flag:

bash
./bin/yalla process --async

Checking Async Execution

php
public function execute(array $input, Output $output): int
{
    if ($this->shouldRunAsync($input)) {
        $output->info('Running in async mode');
        // Async-specific logic
    }

    // Regular execution
    return 0;
}

Parallel Execution

Run multiple operations in parallel using runParallel():

php
public function execute(array $input, Output $output): int
{
    $files = ['file1.txt', 'file2.txt', 'file3.txt'];

    $operations = array_map(function($file) {
        return fn() => $this->processFile($file);
    }, $files);

    try {
        $results = $this->runParallel($operations, $output);

        $output->success('All files processed successfully!');

        return 0;
    } catch (\Exception $e) {
        $output->error('Error processing files: ' . $e->getMessage());
        return 1;
    }
}

protected function processFile(string $file): array
{
    // Process the file
    sleep(2); // Simulate processing

    return ['file' => $file, 'status' => 'completed'];
}

Promises

The executeAsync() method returns a Promise object:

php
$promise = $command->executeAsync($input, $output);

// Wait for completion
$result = $promise->wait();

// Or use callbacks
$promise
    ->then(function($result) {
        echo "Command completed with exit code: " . $result['exitCode'];
    })
    ->catch(function($error) {
        echo "Command failed: " . $error->getMessage();
    });

Progress Callbacks

Show progress during async execution:

php
public function executeAsync(array $input, Output $output): Promise
{
    $promise = parent::executeAsync($input, $output);

    if ($output->isVerbose()) {
        $promise->onProgress(function($progress) use ($output) {
            $output->write('.');
        });
    }

    return $promise;
}

Timeout Configuration

Configure command timeout:

php
class LongRunningCommand extends Command
{
    use SupportsAsync;

    protected int $asyncTimeout = 600; // 10 minutes

    public function execute(array $input, Output $output): int
    {
        // Long running task that may take up to 10 minutes
        $this->processLargeDataset();

        return 0;
    }
}

Error Handling

Handle errors in async commands:

php
public function execute(array $input, Output $output): int
{
    try {
        $result = $this->runParallel([
            fn() => $this->operation1(),
            fn() => $this->operation2(),
            fn() => $this->operation3(),
        ], $output);

        return 0;
    } catch (\Exception $e) {
        $output->error('Async operation failed: ' . $e->getMessage());

        if ($output->isVerbose()) {
            $output->writeln($e->getTraceAsString());
        }

        return 1;
    }
}

Best Practices

1. Use Timeouts Wisely

php
// Set appropriate timeouts based on your use case
protected int $asyncTimeout = 300; // 5 minutes for normal tasks
// protected int $asyncTimeout = 1800; // 30 minutes for heavy processing

2. Handle Failures Gracefully

php
public function execute(array $input, Output $output): int
{
    $operations = [
        fn() => $this->operation1(),
        fn() => $this->operation2(),
    ];

    try {
        $results = $this->runParallel($operations, $output);

        // Check individual results
        foreach ($results as $i => $result) {
            if ($result['error'] ?? false) {
                $output->warning("Operation {$i} failed: {$result['error']}");
            }
        }

        return 0;
    } catch (\Exception $e) {
        return $this->handleException($e, $output);
    }
}

3. Provide User Feedback

php
public function execute(array $input, Output $output): int
{
    $output->info('Starting async processing...');

    if ($this->shouldRunAsync($input)) {
        $spinner = $output->createSpinner('Processing in background...');
        $spinner->start();

        // Async work

        $spinner->success('Background processing complete!');
    }

    return 0;
}

4. Resource Management

php
public function execute(array $input, Output $output): int
{
    $lockManager = new LockManager();

    if (!$lockManager->acquire('async-process', timeout: 60)) {
        $output->error('Another async process is running');
        return 1;
    }

    try {
        // Async operations
        $this->runParallel($operations, $output);

        return 0;
    } finally {
        $lockManager->release('async-process');
    }
}

Limitations

  • Async execution is not true multi-threading; it uses PHP's single-threaded model
  • Long-running async commands may still block if not properly managed
  • Progress callbacks are limited to the main thread execution
  • File descriptors and database connections are shared across async operations

See Also

Released under the MIT License.