★ An async map function
Tags: php, performance, async
Published: 2020-01-02 22:54:08

---
title: ★ An async map function
published: true
date: 2017-12-18 17:21:24 UTC
tags: php, performance, async
canonical_url: https://freek.dev/935-an-async-map-function
---

Laravel has [an excellent Collection class](https://laravel.com/docs/5.5/collections) that has [many useful operations](https://laravel.com/docs/5.5/collections#available-methods). The class is also [macroable](https://laravel.com/docs/5.5/collections#extending-collections). This means that you can add function to it at runtime by calling `macro` on it and passing a name and a closure. In our projects we tend to code up the same macro's over and over again. That's why we have put those macros in [a package called laravel-collection-macros](https://github.com/spatie/laravel-collection-macros) so we, and the community, can reuse them. In this post I'd like to talk a bit about [a new macro](https://github.com/spatie/laravel-collection-macros#parallelmap) that we added today called `parallelMap`.

`parallelmap` is identical to map but each item in the collection will be processed in parallel. Let's take a look at an example:

```
$pageSources = collect($urls)->parallelMap(function($url) {
    return file_get_contents($url);
});
```

The content of the given `$urls` will be fetched at the same time. This will be much faster that fetching the content of one url after the other. Cool stuff!

Here's another piece of code taken from [our tests](https://github.com/spatie/laravel-collection-macros/blob/master/tests/ParallelMapTest.php):

```
/** @test */
public function it_can_perform_async_map_operations()
{
    $this->startStopWatch();

    $collection = Collection::make([1, 2, 3, 4, 5])->parallelMap(function (int $number) {
        sleep(1);

        return $number * 10;
    });

    $this->assertTookLessThanSeconds(2);

    $this->assertEquals([10, 20, 30, 40, 50], $collection->toArray());
}
```

You're probably wondering how this magic works. Well, the hard part is done inside [Amp](https://amphp.org/)'s new package called [`parallel-functions`](https://github.com/amphp/parallel-functions). Here's a short description of what it does taken from [their docs](https://amphp.org/parallel-functions/):

> amphp/parallel-functions is a simplifying layer on top of amphp/parallel. It allows parallel code execution by leveraging threads or processes, depending on the installed extensions. All data sent to / received from the child processes / threads must be serializable using PHP’s `serialize()` function.

Here's an example, again take from their docs, on how you can use the package directly:

```
use Amp\Promise;
use function Amp\ParallelFunctions\parallelMap;

$values = Promise\wait(parallelMap([1, 2, 3], function ($time) {
    \sleep($time); // a blocking function call, might also do blocking I/O here

    return $time * $time;
}));
```

The `parallelMap` macro in our package simply uses their magic. Here's the [definition of the macro](https://github.com/spatie/laravel-collection-macros/blob/b201b7a/src/macros/parallelMap.php):

```
Collection::macro('parallelMap', function (callable $callback): Collection {
    $promises = parallelMap($this->items, $callback);

    $this->items = wait($promises);

    return $this;
});
```

Be aware that you shouldn't use `parallelMap` if the work done in the closure is very simple. Using `parallelMap` causes quite some overhead and is memory intensive. Don't use this for small operations or on a large collection.

Thanks [Niklas Keller](https://twitter.com/kelunik) for [Amp](https://github.com/amphp/amp) and that wonderful [`amphp/parallel-functions`](https://github.com/amphp/parallel-functions).