robdesilets's avatar

dispatchNow vs dispatchSync

Hi,

Pre Laravel 8 I was using dispatchNow. Per the docs, it seems that has been replaced with dispatchSync.

However, before I assumed they were identical I wanted to confirm with other developers.

When I look at the source, I would have expected them to call the same method but one does call dispatchNow and the other dispatchSync.

The source code comments for the dispatchSync also adds "Queuable jobs will be dispatched to the "sync" queue."

Is the sync queue another way of just saying in the current thread/context?

Before I updated my code to use dispatchSync (instead of dispatchNow) I wanted to double check they behaved the same.

(I am assuming dispatchNow will be deprecated in future releases?)

Thanks!

-Rob

0 likes
8 replies
rodrigo.pedra's avatar

dispatchNow

Just call the jobs handle method and return its results.

Doesn't matter if jobs implements ShouldQueue or use the Queuable trait. You can even use the return of the job's handle method in the current thread.

For me the biggest disadvantage of this is if an exception is thrown from the job's handle method, its failed method won't be called, so you need to handle the exception (for loggine purposes, for example) in the scope the job was dispatched.

dispatchSync

If job implements the ShouldQueue interface it will be processed by the queue pipeline in the same thread/process. But it will run through the same pipeline as any other queued job.

For example, if the job uses the Queuable trait, you can have job middlewares, and also rely on the failed method when an exception occurs in the handle method.

I personally start using this to make my synchronous jobs more consistent with the other jobs. Also, if you use job middleware and rely on the job's failed method to handle exceptions, you don't need to worry if those would called when dispatching to a async queue or to the sync connection.

Sample

To illustrate you can try this in your project's ./routes/web.php file:

class MyJob implements \Illuminate\Contracts\Queue\ShouldQueue
{
    use \Illuminate\Bus\Queueable;

    private string $name;
    private bool $shouldFail;

    public function __construct(string $name, bool $shouldFail)
    {
        $this->name = $name;
        $this->shouldFail = $shouldFail;
    }

    public function middleware()
    {
        return [
            function ($job, $next) {
                \dump('middleware: ' . $this->name);

                return $next($job);
            },
        ];
    }

    public function handle()
    {
        if ($this->shouldFail) {
            throw new \RuntimeException($this->name);
        }

        return 'result: ' . $this->name;
    }

    public function failed(\Throwable $exception)
    {
        \dump('from failed: ' . $exception->getMessage());
    }
}

Route::get('test', function (\Illuminate\Contracts\Bus\Dispatcher $dispatcher) {
    $result = $dispatcher->dispatchNow(new MyJob('now', false));

    \dump('results from request: ' . $result);

    \dump('============================');

    try {
        $dispatcher->dispatchNow(new MyJob('now/failing', true));
    } catch (\Throwable $exception) {
        \dump('exception from request: ' . $exception->getMessage());
    }

    \dump('============================');

    $result = $dispatcher->dispatchSync(new MyJob('sync', false));

    \dump('results from request: ' . $result);

    \dump('============================');

    try {
        $dispatcher->dispatchSync(new MyJob('sync/failing', true));
    } catch (\Throwable $exception) {
        \dump('exception from request: ' . $exception->getMessage());
    }
});

The results should be:

"results from request: result: now"

"============================"

"exception from request: now/failing"

"============================"

"middleware: sync"

"results from request: 0"

"============================"

"middleware: sync/failing"

"exception from failed: sync/failing"

"exception from request: sync/failing"

As we can see, for the same job when using dispatchNow the middleware was not executed and the failed method was not called when it failed. But the return from the handle method is available for usage on the request.

While when using dispatchSync, both middleware and failed method were executed as expected. But the return from the handle method is not available in the request.

===

Note: if the job does not use the Queueable trait, the return of the handle method will be available to the request when using dispatchSync. I don't know if it is by design.

Regarding if the dispatchNow will be deprecated in favor of dispatchSync, I guess there is a chance, because only the newer one is in the docs right now.

30 likes
Sammyjo20's avatar

Wow, thank you so much for this post. Especially your note at the end about the Queueable trait. I was having an issue where if I used dispatch_now, I would receive the return value of the handle() method, but I needed to use dispatchSync so failed() would run. But with the Queueable trait, no return value was coming back. Everything seems to work without the Queueable trait so fantastic.

2 likes
cyrrill's avatar

To expand upon this, dispatchSync actually calls dispatchNow so I am not too sure it will be deprecated or see a reason why it would be.

If you look at the source dispatchSync will queue the job if it implements the ShouldQueue interface, else it's just a direct pass-through to dispatchNow

    /**
     * Dispatch a command to its appropriate handler in the current process.
     *
     * Queuable jobs will be dispatched to the "sync" queue.
     *
     * @param  mixed  $command
     * @param  mixed  $handler
     * @return mixed
     */
    public function dispatchSync($command, $handler = null)
    {
        if ($this->queueResolver &&
            $this->commandShouldBeQueued($command) &&
            method_exists($command, 'onConnection')) {
            return $this->dispatchToQueue($command->onConnection('sync'));
        }

        return $this->dispatchNow($command, $handler);
    }

    /**
     * Dispatch a command to its appropriate handler in the current process without using the synchronous queue.
     *
     * @param  mixed  $command
     * @param  mixed  $handler
     * @return mixed
     */
    public function dispatchNow($command, $handler = null)
    {
        $uses = class_uses_recursive($command);

        if (in_array(InteractsWithQueue::class, $uses) &&
            in_array(Queueable::class, $uses) &&
            ! $command->job) {
            $command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync'));
        }

        if ($handler || $handler = $this->getCommandHandler($command)) {
            $callback = function ($command) use ($handler) {
                $method = method_exists($handler, 'handle') ? 'handle' : '__invoke';

                return $handler->{$method}($command);
            };
        } else {
            $callback = function ($command) {
                $method = method_exists($command, 'handle') ? 'handle' : '__invoke';

                return $this->container->call([$command, $method]);
            };
        }

        return $this->pipeline->send($command)->through($this->pipes)->then($callback);
    }
4 likes
jjudge's avatar

despatchNow() is deprecated in Laravel 8, so basically, don't use it anymore.

TheDean's avatar

In my case I discovered that my Job also had to "use Illuminate\Foundation\Bus\Dispatchable"

Otherwise I received an error - Call to undefined method MyJob::dispatchSync()

1 like
NilsBohr14's avatar

I assume, the discussion only raised when we need a return data from job to use in same request cycle synchronously. For a better design choice, we should not use Job if we need the return value in a synchronous way. Instead we should just use a separate class for that.

THIS IS THE FOUNDER'S OPINION

Please or to participate in this conversation.