skip to Main Content

I’m trying to figure out if there’s any chance to receive the status of completion of a task (triggered via an ajax call), via multiple (time intervalled) ajax calls.
Basically, during the execution of something that could take long, I want to populate some variable and return it’s value when asked.

Server code looks like this:

function setTask($total,$current){
    $this->task['total'] = $total;
    $this->task['current'] = $current;
}

function setEmptyTask(){
    $this->task = [];
}

function getTaskPercentage(){
    return ($this->task['current'] * 100) / $this->task['total'];
}

function actionGetTask(){
    if (Yii::$app->request->isAjax) {

        Yii::$app->response->format = yiiwebResponse::FORMAT_JSON;

        return [
            'percentage' => $this->getTaskPercentage(),
        ];
    }
}

Let’s say I’m in a for loop, and I know how many times I iterate over:

function actionExportAll(){
    $size = sizeof($array);
    $c = 0;
    foreach($array as $a){
        // do something that takes relatively long
        $this->setTask($size,$c++);
    }
}

While in the client side i have this:

function exportAll(){
    var intervalId = setInterval(function(){
        $.ajax({
            url: '/get-task',
            type: 'post',
            success: function(data){
                console.log(data);
            }
        });
    },3000);

    $.ajax({
        url: '/export-all',
        type: 'post',
        success: function(data){
            clearInterval(intervalId); // cancel setInterval
            // ..
        }
    });
}

This looks like it could work, besides the fact that ajax calls done in the setInterval function are completed after “export-all” is done and goes in the success callback.
There’s surely something that I’m missing in this logic.
Thanks

3

Answers


  1. Chosen as BEST ANSWER

    So basically I found the solution for this very problem by myself. What you need to do is to replace the above server side's code into this:

    function setTask($total,$current){
        $_SESSION['task']['total'] = $total;
        $_SESSION['task']['current'] = $current;
        session_write_close();
    }
    
    function setEmptyTask(){
        $_SESSION['task'] = [];
        session_write_close();
    }
    
    function getTaskPercentage(){
        return ($_SESSION['task']['current'] * 100) / $_SESSION['task']['total'];
    }
    
    function actionGetTask(){
        if (Yii::$app->request->isAjax) {
    
            Yii::$app->response->format = yiiwebResponse::FORMAT_JSON;
    
            return [
                'percentage' => $this->getTaskPercentage(),
            ];
        }
    }
    

    This works, but I'm not completely sure if is a good practice. From what I can tell, it seems like it frees access to the $_SESSION variable and makes it readable by another session (ence my actionGetTask()) during the execution of the actionExportAll() session.

    Maybe somebody could integrate this answer and tell more about it.

    Thanks for the answers, I will certainly dig more in those approaches and maybe try to make this same task in a better, more elegant and logic way.


  2. The problem is probably in sessions.

    Let’s take a look what is going on.

    1. The request to /export-all is send by browser.
    2. App on server calls session_start() that opens the session file and locks access to it.
    3. The app begins the expensive operations.
    4. In browser the set interval passes and browser send request to /get-task.
    5. App on server tries to handle the /get-task request and calls session_start(). It is blocked and has to wait for /export-all request to finish.
    6. The expensive operations of /export-all are finished and the response is send to browser.
    7. The session file is unlocked and /get-task request can finally continue past session_start(). Meanwhile browser have recieved /export-all response and executes the success callback for it.
    8. The /get-task request is finished and response is send to browser.
    9. The browser recieves /get-task response and executes its success callback.

    The best way to deal with it is avoid running the expensive tasks directly from requests executed by user’s browser.
    Your export-all action should only plan the task for execution. Then the task itself can be executed by some cron action or some worker in background. And the /get-task can check its progress and trigger the final actions when the task is finished.

    You should take look at yiisoft/yii2-queue extension. This extension allows you to create jobs, enqueue them and run the jobs from queue by cron task or by running a daemon that will listen for tasks and execute them as they come.

    Login or Signup to reply.
  3. Without trying to dive into your code, which I don’t have time to do, I’ll say that the essential process looks like this:

    1. Your first AJAX call is “to schedule the unit of work … somehow.” The result of this call is to indicate success and to hand back some kind of nonce, or token, which uniquely identifies the request. This does not necessarily indicate that processing has begun, only that the request to start it has been accepted.

    2. Your next calls request “progress,” and provide the nonce given in step #1 as the means to refer to it. The immediate response is the status at this time.

    3. Presumably, you also have some kind of call to retrieve (and remove) the completed request. The same nonce is once again used to refer to it. The immediate response is that the results are returned to you and the nonce is cancelled.

    Obviously, you must have some client-side way to remember the nonce(s). “Sessions” are the most-common way to do that. “Local storage,” in a suitably-recent web browser, can also be used.

    Also note … as an important clarification … that the title to your post does not match what’s happening: one AJAX call isn’t happening “during” another AJAX call. All of the AJAX calls return immediately. But, all of them refer (by means of nonces) to a long-running unit of work that is being carried out by some other appropriate means.

    (By the way, there are many existing “workflow managers” and “batch processing systems” out there, open-source on Github, Sourceforge, and other such places. Be sure that you’re not re-inventing what someone else has already perfected! “Actum Ne Agas: Do Not Do A Thing Already Done.” Take a few minutes to look around and see if there’s something already out there that you can just steal.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search