-
-
Notifications
You must be signed in to change notification settings - Fork 3
Chained promises
The promise methods then()
, catch()
and finally()
are used to execute functionality in the future, once the promise is resolved. The callback passed to then()
will execute when the promise successfully completes its work, catch()
will execute if there was an error, and finally()
will execute whether or not there was success or failure.
An obvious example of when a promise can be used to defer work is in asynchronous HTTP calls, such as in the Fetch implementation.
Example:
// Make an HTTP request to example.com and when it returns a response,
// pass it to the `then` function:
$http->fetch("https://example.com/json")->then(function(Response $response) {
if(!$response->ok) {
die("There was an issue! Error code $response->code");
}
echo "Response received with HTTP status code $response->code!";
});
In the example above, the Response
object will be passed to the then()
function as soon as it resolves. Fetch is a good example of chained promises, because even though the Response
object has resolved, the body of the response may still be downloading, so it's the job of the callback to return a new Promise that resolves with the correct type of data. This is how fetch works - see the MDN documentation page for making a basic request.
Let's assume that the response type we want is a JsonObject
. In our initial promise callback we can return a new promise that resolves with the correct data type. This is built in to the PHP.GT/Fetch functionality:
$http->fetch("https://example.com/json")->then(function(Response $response) {
if(!$response->ok) {
die("There was an issue! Error code $response->code");
}
return $response->json();
})->then(function(JsonObject $json) {
echo "Received JSON object with " . count($json->getArray("results")) . " results!";
});
In the example above, we have introduced promise chaining: there are two then
functions where the first resolves with a Response
and the second resolves with a JsonObject
, because the first callback returns with a new promise. Calling Response::json()
will return a new promise, but any promise can be returned.
It's also possible to return a static value directly from a then
callback. Because PHP function parameters can be type-hinted, only the function that has the correct type hint for the resolved value will be called.
Here's an example of promise chaining with type safety:
$promise
->then(function(string $value) {
// Returns an int, so the next int-resolving then function will execute.
return 123;
})
->then(function(int $number) {
// Returns a string, so the float-resolving function will be skipped.
return $number . "-handled";
})
->then(function(float $number) {
// This function will never execute, because no previous then functions in the chain resolve with a float.
echo "Float handler: $number\n";
})
->then(function(string $value) {
// This function is called due to an earlier function in the chain returning a string,
// but it doesn't return anything itself so ends the chain.
echo "Final string: $value\n";
})
->then(function(string $value) {
// This function will never execute, because the previously called function in the chain has a null/void return.
echo "Another string: $value\n";
};
Two features are shown in this example: 1) only the correct matching function in the chain will execute according to the function's parameter type, and 2) returning nothing, or null, will terminate the chain so no future then
functions execute.
Note that there isn't any benefit to using promises in the above example, because none of the code executes asynchronously, but the concept of promise chaining is easily explained when working with static values like this.
Working with promises is only useful when done asynchronously, and to do that requires an async event loop.
PHP.GT/Promise is a separately maintained component of PHP.GT/WebEngine.