How to implement JavaScript-style Promises on PHP

Jose Luis
4 min readMay 16, 2022

--

Introduction (tl;dr)

In one of the last modules that I built for legacy software, I face the following problem and I would like to share the solution with you

The problem itself was the following: I had very long tasks that would be triggered by different endpoints, moreover, each endpoint had to execute two or more tasks in parallel (very long tasks). In case it executed all the tasks synchronously, it would become very slow and inefficient. So to solve that I built JavaScript-style promises for PHP Yes, I know I can use NodeJS, but that was not an option for me, this module has to be made in PHP, and that was one of the limitations.

In this post, I will show you how to implement JavaScript-style promises that will allow you to execute asynchronous code in PHP.

We are going to use the following modules that will help us:

Note: This code will not work on Windows, due to the fact that some of the modules that we are going to use work only on UNIX systems

Let’s go

Let’s start by defining an interface that will help us write the class itself. It is useful to start with the interface, so we can have in mind the methods that we will need

Well, now that we have the necessary methods, we will need to create a class that implements this interface

As we can see, I started by defining the empty methods. All the code will start in the constructor, so we can create a promise by writing something like this:

Let’s start by writing the constructor.

Let’s explain what's happening here:

  1. We check if the module pcntl_fork is available. If not, we throw an exception
  2. We try to fork and store the ID in a variable to check if the process was forked.
  3. If the process was forked, we call the user function. If not we throw an exception

So far, the code looks good and will make the user’s function run in another process, but it has a problem, we can’t share data between processes, and we can’t know if the promise has been resolved, or the value it was resolved with.

In order to solve this, we will make use of shared memory. The first thing that we are going to need is to attach a resource where we are going to

Let’s explain what’s going on again.

  1. We have added a property that will store the resource of the shared memory
  2. In the constructor, we call the method attach. This method will get a unique name and generate a temp file then we attach the shared memory segment. At this point, all we have done is create a temporary memory space where we can store data and we have done this before fork de process so we can have the resource in both, the main process and the forked process.

Now that we have the shared memory created, we can use these two methods to set and get values (and that values are going to be accessible across processes). That methods are: shm_put_var and shm_get_var.

Let’s explain what we have done.

  1. First, we have added properties that will contain unique keys for Value, Error and State. These are going to be useful for reading and storing data in shared memory.
  2. We have created a method that generates the unique keys for those properties, and we have called this method in the constructor.
  3. The next thing we have done is to encapsulate the setters and getters methods.

So let’s review what we have at this point.

  1. We can create code that runs in another process
  2. We can share data between process

It’s fine at this point, but we haven’t yet done the interface methods. So the next thing done is to implement the methods from the interface.

The only thing we have done is to use the previously defined getters and setters and create the Wait method that will loop until the promise is resolved or rejected, note that the Wait method has a usleep, if this usleep is not present it would cause the CPU to be set to 100% at times.

So now, you can create async code and wait until this is finished.

Let’s show some examples

In this case, we created two promises that will execute two long tasks in parallel. The main function will not stop its execution until the two promises were resolved.

In this example, the two long tasks will be executed in parallel, but they are not being waited

I’ve created a Composer Package so you can use this class in your projects.

https://packagist.org/packages/srdestino/waitable
https://github.com/JoseHuertasDev/Waitable

--

--