How to Run Heavy Functions on Repeatable Action Hooks

Sometimes when you’re using some hooks, for example save_post, you may notice that functions connected to those hook may be fired twice, for example it happens when you’re using outdated metaboxes instead of Gutenberg plugin sidebars.

But there is no need to worry about save_post hook because it can be fixed pretty easily – depending whether you’re using the Block Editor or not, just create meta fields the appropriate way and it fixes everything… it is woocommerce_update_order hook I am worried about.

When you create or update a WooCommerce order, the woocommerce_update_order action hook is going to be fired multiple times and at least it may create performance issues.

If you want to get a more clear understanding what do I mean by that, just try to run the following code on your WooCommerce store:

add_action( 'woocommerce_update_order', function( $order_id ) {
	
	$email = ''; // set your test email address here
	wp_mail( $email, 'Test email', sprintf( 'Order %d is updated', $order_id ) );

} );

Then just go ahead and create or update an existing order on your store. Your inbox will be overwhelmed with test emails 😁 Just like this:

Emails on order update hook
That’s how many emails I got when I just updated an order status in the WordPress admin dashboard.

But let’s assume that we need to run some kind of heavy function and there is no way to do it except with the woocommerce_update_order hook. Guess what happens?

add_action( 'woocommerce_update_order', function( $order_id ) {
	
	sleep( 2 ); // or some kind of heavy function

} );

Your code will be run multiple times and believe me you’ll be tired every time waiting while your heavy code is running 5 times in row (when only one iteration is necessary).

There are actually two methods how to deal with that and I am not sure that the first method is really that good, so you can just go ahead and jump straight to the second one 😁

Remove Upcoming Actions Inside the Function (First Method)

This is the simplest method how you can prevent your custom function connected to the woocommerce_update_order hook from running multiple times.

add_action( 'woocommerce_update_order', 'rudr_order_update', 25, 2 );

function rudr_order_update( $order_id, $order ) {
	
	remove_action( 'woocommerce_update_order', __FUNCTION__, 25, 2 );

	// do your stuff and don't worry about anything
	
	// this function won't run anymore within the current request

}

Previously I also mentioned that this is not the best way to prevent performance issues, why?

Let’s assume that the hook is running 5 times:

  1. woocommerce_update_order – here you run your function and then disconnect it,
  2. woocommerce_update_order,
  3. woocommerce_update_order,
  4. woocommerce_update_order – maybe here WooCommerce does something important with an order you need to keep track of in your function but you can not do that because you’ve unconnected it on the first hook iteration!
  5. woocommerce_update_order.

Now it should be clear πŸ™ƒ But if it is not the case for you, for sure, you can use the first method, otherwise let’s just dive into the second one.

Using Cron to Run the Function (Second method)

If you try to look into WooCommerce code, you will see that the plugin also uses this method. So unless in the future we don’t get some unique hooks for order updates, this method seems like an optimal way.

And we’re talking about WooCommerce there are two ways how you can run cron jobs – with the Action Scheduled which is included in WooCommerce or with the standard WP Cron.

Let’s take a look at both ways.

Action Scheduler

add_action( 'woocommerce_update_order', 'rudr_maybe_order_update' );

function rudr_maybe_order_update( $order_id ) {
	
	as_schedule_single_action( 
		time() + 5, // when to run? 5 seconds later
		'rudr_update_order', // the hook name
		array( $order_id ), // arguments to pass to the hook and the function
		'', // group name, could be any string
		true // should be unique? – yes
	);

}

add_action( 'rudr_update_order', function( $order_id ) {
	
	$order = wc_get_order( $order_id );

	// do your stuff here
	
} );

Some explanation:

WP Cron

If for some reason you don’t want to use Action Scheduler here, you can still use a good old WordPress Cron.

And the code is going to look very similar, the only change – one extra condition to check whether the event is already scheduled or not.

add_action( 'woocommerce_update_order', 'rudr_maybe_order_update' );

function rudr_maybe_order_update( $order_id ) {
	
	$hook_name = 'rudr_update_order';
	$args = array( $order_id );
	
	if( ! wp_next_scheduled( $hook_name, $args ) {
		wp_schedule_single_event( time() + 5, $hook_name, $args );
	}

}

add_action( 'rudr_update_order', function( $order_id ) {
		
	$order = wc_get_order( $order_id );

	// do your stuff here
	
} );

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X