【问题标题】:How to delay PHP function from triggering in a jQuery Ajax Loop?如何延迟 PHP 函数在 jQuery Ajax 循环中触发?
【发布时间】:2018-12-18 18:50:41
【问题描述】:

我有一个临时上传 CSV 的 PHP 脚本。一页重载CSV数据来自$_FILES并转换为JSON数组。

然后我使用 $.each 遍历 CSV 行。

对于每一行,我都在对 PHP 函数进行 AJAX 调用,该函数设置一些订单跟踪数据并发送电子邮件。

由于电子邮件限制,我想在每次循环迭代之间添加延迟。但是,我尝试在 JavaScript 中使用设置的超时来执行此操作,但它不起作用,并且还尝试在发送电子邮件之前添加 PHP 睡眠功能。

两者都不起作用,电子邮件仍然可以同时发送,没有延迟。

无论我添加的延迟如何,我发出的所有请求都会被立即处理。

如何确保邮件发送延迟?

jQuery($csv_rows 是刚刚上传的 CSV 数据)

<script>

    // Get CSV Rows into JSON array

    var csvRows = '<?php echo json_encode( $csv_rows ); ?>';
    var csvRows = ( jQuery.parseJSON( csvRows ) );

    // Loop through each row

    $.each( csvRows, function( key, value ) {

        // Split row into array exploded by comma

        row = value.toString().split( ',' );

        // Get column values

        order = row[0];
        courier = row[1];
        tracking = row[2];

        // AJAX

        var data = {
            'action': 'shd_tracking_import',
            'order': order,
            'courier': courier,
            'tracking': tracking,
        };

        // Do the ajax

        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: data,
            success: function( response ) {
                $( '#shd-import-results p' ).hide();
                if( response !== '0' ) {
                    $( '#shd-import-results ul' ).append( response );
                    importedCount = parseInt( $( '#shd-import-progress span' ).text() );
                    $( '#shd-import-progress span' ).text( importedCount + 1 );
                } else {
                    $( '<p>Error importing. Please ensure CSV meets requirements.</p>' ).appendTo( '#shd-import-results' );
                }
            }
        });

    });

</script>

PHP(这是 AJAX 中引用的 shd_tracking_import 操作)

if( isset( $_POST['order'] ) && isset( $_POST['courier'] ) && isset( $_POST['tracking'] ) ) {

    // Delay (due to their Office 365 limits)

    usleep( 4000000 ); // 4 Seconds (usleep used as sleep cannot contain fractions, usleep is microseconds, this was 2.5 seconds hence using usleep)

    // My mailing function is here (which works just not delayed)

    echo 'Done';

} else {

    echo '0';

}

exit;

【问题讨论】:

  • 这是一种非常奇怪的做法,处理 CSV 文件服务器端并在那里循环会不会更容易,只需在 js/frontend 中有一个按钮来激活它吗?此外,我了解您如何受到 Office 365 的限制,但我不认为它们是 ESP(电子邮件服务提供商),请研究适合群发电子邮件的 ESP(可以访问 API 等)
  • 更好地实现一个在指定时间后处理作业的作业队列
  • 这应该会延迟电子邮件。尝试延迟 4 秒以上注意延迟,60 秒可能有利于测试。接收邮件服务器可能会将电子邮件排队,因此您认为它们是同时到达的
  • @Accountantمsort of,js 是异步的,所以它不会等待 php 的回复来调用下一行,所以它们之间偏移了 4 秒,而不是 4 秒
  • @DarkMukke 是的,你是对的,我认为 php 中的延迟是在发送电子邮件的循环内

标签: php jquery ajax


【解决方案1】:

您可以在 jquery 中使用 Promise,当 API 执行完成时,promise 代码将执行。

查看示例

var div = $( "<div>" );

div.promise().done(function( arg1 ) {
  // Will fire right away and alert "true"
  alert( this === div && arg1 === div );
});

【讨论】:

  • 解决实际问题的方法,我认为问题问错了。
  • 应该在服务器端完成,但确实解决了OP面临的问题。
  • 我会试试看的。
【解决方案2】:

虽然 Piyush 的回答是正确的,但我仍然会在这个服务器时间。 我理解您为什么希望看到进度,并且有几个选项。

例如:

<?php

$file = tempnam(__DIR__, 'csv-upload');
$fh = fopen($file, 'a+b');
foreach ($csv_rows as $row) {
    fputcsv($fh, $row);
}
fclose($fh);
$rowCount = count($csv_rows);
$file = str_replace(__DIR__, '', $file);

?>
<script>
    var csvTotal = <?= $rowCount; ?>,
        csvCount = 0,
        end = false
    ;

    var looper = requestTimeout(function () {
        if (end || csvCount >= csvTotal) {
            clearTimeout(looper);
            return;
        }

        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {'file': '<?= $file; ?>'},
            success: function (response) {
                response = JSON.parse(response);
                if (!response.success) {
                    end = true;
                    $('<p>' + response.msg + '</p>').appendTo('#shd-import-results');
                } else {
                    csvCount = response.msg;
                    $('#shd-import-progress span').text(csvCount);
                }
            }
        });
    }, 4000);

</script>

和 ajax:

<?php

session_start();
ignore_user_abort(true);
set_time_limit(0);

//filename as unique key, so you can have mutiple queues at the same time
if (!isset($_POST['file'])) {
    echo json_encode([
        'success' => false,
        'msg' => 'File name required'
    ]);
    die();
}
$file = __DIR__ . $_POST['file'];
if (!file_exists($file)) {
    echo json_encode([
        'success' => false,
        'msg' => 'File does not excist'
    ]);
    die();
}


if (!isset($_SESSION['email_status'], $_SESSION['email_status'][$file])) {
    $fh = fopen($file, 'rb');
    $_SESSION['email_status'][$file] = 0;
    while ($row = fgetcsv($fh)) {
        $order = $row['order'];
        $courier = $row['courier'];
        $tracking = $row['tracking'];

        mail();
        $_SESSION['email_status'][$file]++;
        //4 seconds delaye
        usleep(4000);
    }
    fclose($fh);
    unlink($file);
} else {
    echo json_encode([
        'success' => false,
        'msg' => $_SESSION['email_status'][$file]
    ]);
}

在会话中,它只会启动一次电子邮件。这里的问题是,当会话/cookies被清除时,它会再次运行它,所以最好使用不同的存储机制(redis,或者写一个pid文件或其他什么),但它应该说明我是什么努力实现。

【讨论】:

    【解决方案3】:

    您的脚本同时运行所有实例,所有这些延迟 4 秒,但异步。

    您的延迟应该在 JavaScript 中,每 4 秒调用一次 PHP 文件。

    【讨论】:

      【解决方案4】:

      在浏览完答案后,我决定以不同的方式看待这个问题,因为我不想使用会话,也不能在 $.each 上使用 .promise()。于是我把代码改写成如下。

      这通过从 CSV 获取总行数,运行 AJAX 一次,然后我在 ajax 上使用 .done() 重新运行 AJAX 调用,直到它通过所有行,这可以按我的意愿工作。

      p>
      <script>
      
          // Get CSV Rows into JSON array
      
          var csvRows = '<?php echo json_encode( $csv_rows ); ?>';
          var csvRows = ( jQuery.parseJSON( csvRows ) );
          var totalRows = csvRows.length - 1; // Minus 1 as starts 1, we need to start from 0 (e.g. for 4 rows this returns 4, when row 4 is actually 3 as starts from 0)
      
          // Do AJAX function
      
          function doAjax( $row ) {
      
              // Error
      
              var error = false;
      
              // Get row data
      
              order = csvRows[row][0];
              courier = csvRows[row][1];
              tracking = csvRows[row][2];
      
              // AJAX
      
              var data = {
                  'action': 'shd_tracking_import',
                  'order': order,
                  'courier': courier,
                  'tracking': tracking,
              };
      
              // Do the ajax
      
              $.ajax({
                  url: ajaxurl,
                  type: 'POST',
                  data: data,
                  success: function( response ) {
                      $( '#shd-import-results p' ).hide();
                      if( response !== '0' ) {
                          $( '#shd-import-results ul' ).append( response );
                          importedCount = parseInt( $( '#shd-import-progress span' ).text() );
                          $( '#shd-import-progress span' ).text( importedCount + 1 );
                      } else {
                          error = true;
                      }
                  }
              }).done( function() {
      
                  if( error !== true ) {
      
                      // When AJAX is done
      
                      var dt = new Date();
                      var time = dt.getHours() + ":" + dt.getMinutes() + ":" + dt.getSeconds();
      
                      // Row number total
      
                      row = row + 1;
      
                      // If row number is less than or equal to the total row count, if not do nothing, no further call.
      
                      if( row <= totalRows ) {
      
                          console.log( 'Next AJAX about to run @ ' + time );
                          doAjax( row );
      
                      } else {
      
                          $( '<span> - Import complete.</span>' ).appendTo( '#shd-import-progress' );
      
                      }
      
                  } else {
      
                      $( '<span> - Import failed, check CSV meets requirements.</span>' ).appendTo( '#shd-import-progress' );
      
                  }
      
              });
      
          }
      
          // Start row at zero
      
          row = 0;
      
          // Do Ajax (once row is done next row will run same AJAX function until all rows done)
      
          doAjax( row );
      
      </script>
      

      【讨论】:

      • 这是@Piyush 所建议的,只是没有你所有的代码,你仍然应该在服务器端进行,原因有很多。如果这是在慢速设备上运行的,并且文件很大,它将产生巨大的影响。 Sessions 也不是唯一的选择,例如 memcache、redis、(pid) 文件、mysql、sqlite 等
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-08
      • 1970-01-01
      • 2023-04-03
      • 2011-11-06
      • 2012-09-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多