19

TL;DR: How can I return data from a queued Job without saving it anywhere and handling the situation when the job might run more than once because of retries? Or is there another way if Jobs are not suitable for this?


I am developing API on Laravel for mobile application.

Methods will make requests to other API's, combine and filter data, changing it's structure etc.

One of the requirements to app is to respond no more than 30 seconds, or not respond at all. So, I have to repeat requests as much as I have time. I trying to realize that with Laravel Queues, and currently have something like that in my Job class:

private $apiActionName;

public function __construct($apiActionName)
{
    $this->apiActionName = $apiActionName;
}

public function handle(SomeService $someService)
{
    return $someService->{$this->apiActionName}();
}

And this action code in controller:

public function someAction()
{ 
    $data = $this->dispatch(new MyJob($apiActionName));
    return response()->json($data);
}

Yes, I know it is bad idea to return value from job, but expect that it's possible. However $this->dispatch() returns only queued job ID, not result of handle method.

Tony
  • 9,672
  • 3
  • 47
  • 75
Bushikot
  • 783
  • 3
  • 10
  • 26
  • 1
    If you want to handle asynchronously, I built this https://github.com/williamjulianvicary/laravel-job-response which will use a cache layer for transporting the response back to your original task (and block until it's ready). – williamvicary Jun 25 '20 at 08:32
  • If you need to run the job asynchronously, there's no way for it to return a value. I think you need to provide more context into why you need a value, as that could help us provide you a solution that would work. – Oddman Aug 26 '23 at 01:12

3 Answers3

14

You are returning data in your Job class, but assigning $data to a dispatcher - note that dispatch() method is not a part of your Job class.

You could try something like this, assuming that your jobs run synchronously:

private $apiActionName;
private $response;

public function __construct($apiActionName)
{
    $this->apiActionName = $apiActionName;
}

public function handle(SomeService $someService)
{
    $this->response = $someService->{$this->apiActionName}();
}

public function getResponse()
{
    return $this->response;
}

And then in your controller:

public function someAction()
{ 
    $job = new MyJob($apiActionName);
    $data = $this->dispatch($job);
    return response()->json($job->getResponse());
}

Obviously, this won't work once you move to async mode and queues - response won't be there yet by the time you call getResponse(). But that's the whole purpose of async jobs :)

Denis Mysenko
  • 6,366
  • 1
  • 24
  • 33
  • 1
    Thank you for response! The problem that I need to run it in async mode, because otherwise Job will not repeat in case of failure. I tried to follow your recommendation with getter, and then check getResponse() in a loop, but it doesn't work (retries have other id's?). By the way, it's possible to return value from non-queued jobs, without getter: $data = $this->dispatchNow($job); – Bushikot May 20 '16 at 12:17
  • 3
    Will, in async mode you won't be able to return a value by definition - because it will be run some time later, in async mode. Therefore, your job has to save the result somewhere and a client/controller has to ask for it again, either via polling or real-time streaming (websockets, pubsub). Either way, you can't just return a result from an async job – Denis Mysenko May 20 '16 at 13:40
  • 2
    Tried this with **Laravel 5.8**, didn't work. You need to use `$this->dispatchNow` instead of `$this->dispatch` to get an updated value back from $job->getResponse(); – mwallisch Dec 09 '19 at 22:40
  • What happens if the Job has a Rate Limiter and if this limit is reached makes a ```$this->relase($seconds = 10)```? – MrEduar Jul 19 '20 at 19:39
7

Laravel 7 and prior

If you are using a queue driver other than sync and you're using Laravel version 7 or prior, you can use the @Denis Mysenko approach and the method dispatchNow to get the data from the Job object that it works well.

Laravel 8

However, in Laravel version 8, the dispatchNow method was deprecated in favor of dispatchSync. But, this new method creates a new instance of the Job and you won't be able to access this new instance or any of its properties from the client script.

Solution

But, accordingly to the rodrigo.pedra's answer, if you remove the Illuminate\Bus\Queueable trait from your job, you will be able to return the data from your job handle method and use this result in your client script.

if the job does not use the Queueable trait, the return of the handle method will be available to the request when using dispatchSync - see this answer.

<?php
// ...

class MyJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, SerializesModels;
    // use Queueable
    /* do not use ^^ this trait */

    private $apiActionName;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($apiActionName) 
    {
        $this->apiActionName = $apiActionName;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle() 
    {
        // operations generating result
        $result = $someService->{$this->apiActionName}();
 
        return $result;
    }
}

in the client script

public function someAction()
{ 
    $result = MyJob::dispatchSync($apiActionName);
    return response()->json($result);
}

Note: the dispatchSync method is the same as using dispatch()->onQueue('sync') which will force the Queue system to use the sync driver, running the job immediately.

Wesley Gonçalves
  • 1,985
  • 2
  • 19
  • 22
4

If you want return data from Laravel jobs, you need write some Queue methods at Providers/AppServiceProvider.php inside boot method (Laravel 7.x / 8)

public function boot()
{
   Queue::before(function ( JobProcessing $event ) {
      Log::info('Job ready: ' . $event->job->resolveName());
      Log::info('Job started: ' . $event->job->resolveName());
   });
    
   Queue::after(function ( JobProcessed $event ) {
      Log::notice('Job done: ' . $event->job->resolveName());
      Log::notice('Job payload: ' . print_r($event->job->payload(), true));
   });
    
   Queue::failing(function ( JobFailed $event ) {
       Log::error('Job failed: ' . 
                  $event->job->resolveName() . 
                 '(' . $event->exception->getMessage() . ')'
                 );
   });
}
data one
  • 93
  • 6