Облегченная реализация CommonJS Promises/A для PHP.
Promise — это библиотека, реализующая CommonJS Promises/A для PHP.
Он также предоставляет несколько других полезных концепций, связанных с обещаниями, таких как объединение нескольких обещаний, сопоставление и сокращение коллекций обещаний.
Если вы никогда раньше не слышали об обещаниях, сначала прочитайте это.
Отложенное представляет собой вычисление или единицу работы, которая, возможно, еще не завершена. Обычно (но не всегда) эти вычисления выполняются асинхронно и завершаются в какой-то момент в будущем.
В то время как deferred представляет собой само вычисление, Promise представляет собой результат этого вычисления. Таким образом, каждый отложенный вызов имеет обещание, которое выступает в качестве заполнителя для его фактического результата.
Отложенная операция представляет собой операцию, разрешение которой ожидается. Он имеет отдельные части обещания и преобразователя.
$ deferred = new React Promise Deferred ();
$ promise = $ deferred -> promise ();
$ deferred -> resolve (mixed $ value );
$ deferred -> reject (Throwable $ reason ); Метод promise возвращает обещание отложенного объекта.
Методы resolve и reject управляют состоянием отложенного процесса.
Конструктор Deferred принимает необязательный аргумент $canceller . См. «Обещание» для получения дополнительной информации.
$ promise = $ deferred -> promise ();Возвращает обещание отложенного объекта, которое вы можете передать другим, сохраняя за собой право изменять его состояние.
$ deferred -> resolve (mixed $ value ); Разрешает обещание, возвращаемое функцией promise() . Все потребители уведомляются посредством вызова $onFulfilled (который они зарегистрировали через $promise->then() ) с $value .
Если $value само по себе является обещанием, то обещание перейдет в состояние этого обещания после его разрешения.
См. также resolve() .
$ deferred -> reject (Throwable $ reason ); Отклоняет обещание, возвращаемое функцией promise() , сигнализируя о том, что вычисление отложенного метода не удалось. Все потребители уведомляются посредством вызова $onRejected (который они зарегистрировали через $promise->then() ) с $reason .
См. также функцию reject() .
Интерфейс обещаний предоставляет общий интерфейс для всех реализаций обещаний. См. «Обещание», где представлена единственная общедоступная реализация, предоставляемая этим пакетом.
Обещание представляет собой конечный результат, который представляет собой либо выполнение (успех) и связанную с ним ценность, либо отказ (неудача) и связанную с ним причину.
Находясь в состоянии «выполнено» или «отклонено», обещание становится неизменным. Ни его состояние, ни его результат (или ошибка) не могут быть изменены.
$ transformedPromise = $ promise -> then (callable $ onFulfilled = null , callable $ onRejected = null );Преобразует значение обещания, применяя функцию к значению выполнения или отклонения обещания. Возвращает новое обещание для преобразованного результата.
Метод then() регистрирует новые обработчики выполнения и отклонения с помощью обещания (все параметры необязательны):
$onFulfilled будет вызван после выполнения обещания и передачи результата в качестве первого аргумента.$onRejected будет вызываться после отклонения обещания и передачи причины в качестве первого аргумента. Он возвращает новое обещание, которое будет выполнено с возвращаемым значением либо $onFulfilled , либо $onRejected , в зависимости от того, какое из них будет вызвано, или будет отклонено с выброшенным исключением, если одно из них выдаст.
Промис дает следующие гарантии обработчиков, зарегистрированных в одном вызове then() :
$onFulfilled или $onRejected , но не оба.$onFulfilled и $onRejected никогда не будут вызываться более одного раза. $ promise -> catch (callable $ onRejected );Регистрирует обработчик отклонения для обещания. Это ярлык для:
$ promise -> then ( null , $ onRejected ); Кроме того, вы можете ввести подсказку в аргумент $reason функции $onRejected чтобы перехватывать только определенные ошибки.
$ promise
-> catch ( function ( RuntimeException $ reason ) {
// Only catch RuntimeException instances
// All other types of errors will propagate automatically
})
-> catch ( function ( Throwable $ reason ) {
// Catch other errors
}); $ newPromise = $ promise -> finally (callable $ onFulfilledOrRejected );Позволяет выполнять задачи типа «очистка» в цепочке обещаний.
Он обеспечивает вызов $onFulfilledOrRejected без аргументов, когда обещание либо выполнено, либо отклонено.
$promise выполняется и $onFulfilledOrRejected успешно возвращается, $newPromise выполнится с тем же значением, что и $promise .$promise выполняется, а $onFulfilledOrRejected выдает или возвращает отклоненное обещание, $newPromise отклонит его с выброшенным исключением или причиной отклонения обещания.$promise отклоняется, а $onFulfilledOrRejected успешно возвращается, $newPromise отклонит по той же причине, что и $promise .$promise отклоняет, а $onFulfilledOrRejected выдает или возвращает отклоненное обещание, $newPromise отклонит с выброшенным исключением или причиной отклонения обещания. finally() ведет себя аналогично синхронному оператору Final. В сочетании с catch() finally() позволяет писать код, похожий на знакомую синхронную пару catch/finally.
Рассмотрим следующий синхронный код:
try {
return doSomething ();
} catch ( Throwable $ e ) {
return handleError ( $ e );
} finally {
cleanup ();
} Аналогичный асинхронный код (с doSomething() , возвращающим обещание) можно написать:
return doSomething ()
-> catch ( ' handleError ' )
-> finally ( ' cleanup ' ); $ promise -> cancel (); Метод cancel() уведомляет создателя промиса о том, что результаты операции его больше не интересуют.
Как только обещание выполнено (либо выполнено, либо отклонено), вызов метода cancel() для обещания не имеет никакого эффекта.
Устарело с версии 3.0.0, вместо этого см.
catch().
Метод otherwise() регистрирует обработчик отклонения обещания.
Этот метод продолжает существовать только по причинам BC и для облегчения обновления между версиями. Это псевдоним для:
$ promise -> catch ( $ onRejected );Устарело с версии 3.0.0, вместо этого см.,
finally().
Метод always() позволяет выполнять задачи типа «очистки» в цепочке обещаний.
Этот метод продолжает существовать только по причинам BC и для облегчения обновления между версиями. Это псевдоним для:
$ promise -> finally ( $ onFulfilledOrRejected ); Создает обещание, состояние которого контролируется функциями, передаваемыми в $resolver .
$ resolver = function ( callable $ resolve , callable $ reject ) {
// Do some work, possibly asynchronously, and then
// resolve or reject.
$ resolve ( $ awesomeResult );
// or throw new Exception('Promise rejected');
// or $resolve($anotherPromise);
// or $reject($nastyError);
};
$ canceller = function () {
// Cancel/abort any running operations like network connections, streams etc.
// Reject promise by throwing an exception
throw new Exception ( ' Promise cancelled ' );
};
$ promise = new React Promise Promise ( $ resolver , $ canceller );Конструктор обещания получает функцию разрешения и необязательную функцию отмены, которые будут вызываться с двумя аргументами:
$resolve($value) — основная функция, определяющая судьбу возвращенного обещания. Принимает либо необещанное значение, либо другое обещание. При вызове с необещанным значением выполняет обещание с этим значением. При вызове с другим промисом, например $resolve($otherPromise) судьба промиса будет аналогична судьбе $otherPromise .$reject($reason) — Функция, отклоняющая обещание. Рекомендуется просто генерировать исключение вместо использования $reject() .Если преобразователь или отменитель выдает исключение, обещание будет отклонено с указанием этого выброшенного исключения в качестве причины отклонения.
Функция разрешения будет вызвана немедленно, функция отмены — только после того, как все потребители вызовут метод cancel() обещания.
Полезные функции для создания и объединения коллекций промисов.
Все функции, работающие с коллекциями обещаний (например, all() , race() и т. д.), поддерживают отмену. Это означает, что если вы вызываете cancel() для возвращенного обещания, все обещания в коллекции будут отменены.
$ promise = React Promise resolve (mixed $ promiseOrValue ); Создает обещание для предоставленного $promiseOrValue .
Если $promiseOrValue является значением, это будет значение разрешения возвращаемого обещания.
Если $promiseOrValue является thenable (любой объект, предоставляющий метод then() ), возвращается доверенное обещание, которое следует за состоянием thenable.
Если $promiseOrValue — это обещание, оно будет возвращено как есть.
Полученный $promise реализует PromiseInterface и может использоваться как любое другое обещание:
$ promise = React Promise resolve ( 42 );
$ promise -> then ( function ( int $ result ): void {
var_dump ( $ result );
}, function ( Throwable $ e ): void {
echo ' Error: ' . $ e -> getMessage () . PHP_EOL ;
}); $ promise = React Promise reject (Throwable $ reason ); Создает отклоненное обещание по указанной $reason .
Обратите внимание, что интерфейс Throwable представленный в PHP 7, охватывает как пользовательские Exception , так и внутренние ошибки PHP Error . Если использовать Throwable в качестве причины отклонения обещания, любая языковая ошибка или исключение пользовательской области может быть использовано для отклонения обещания.
Полученный $promise реализует PromiseInterface и может использоваться как любое другое обещание:
$ promise = React Promise reject ( new RuntimeException ( ' Request failed ' ));
$ promise -> then ( function ( int $ result ): void {
var_dump ( $ result );
}, function ( Throwable $ e ): void {
echo ' Error: ' . $ e -> getMessage () . PHP_EOL ;
}); Обратите внимание, что отклоненные обещания всегда должны обрабатываться аналогично тому, как любые исключения всегда должны перехватываться в блоке try + catch . Если вы удалите последнюю ссылку на отклоненное обещание, которое не было обработано, оно сообщит об отклонении необработанного обещания:
function incorrect (): int
{
$ promise = React Promise reject ( new RuntimeException ( ' Request failed ' ));
// Commented out: No rejection handler registered here.
// $promise->then(null, function (Throwable $e): void { /* ignore */ });
// Returning from a function will remove all local variable references, hence why
// this will report an unhandled promise rejection here.
return 42 ;
}
// Calling this function will log an error message plus its stack trace:
// Unhandled promise rejection with RuntimeException: Request failed in example.php:10
incorrect (); Отклоненное обещание будет считаться «обработанным», если вы уловите причину отклонения с помощью метода then() , метода catch() или метода finally() . Обратите внимание, что каждый из этих методов возвращает новое обещание, которое может быть снова отклонено, если вы повторно создадите исключение.
Отклоненное обещание также будет считаться «обработанным», если вы прервете операцию с помощью метода cancel() (который, в свою очередь, обычно отклоняет обещание, если оно все еще ожидает выполнения).
См. также функцию set_rejection_handler() .
$ promise = React Promise all (iterable $ promisesOrValues ); Возвращает обещание, которое будет выполнено только после того, как будут решены все элементы в $promisesOrValues . Значение разрешения возвращаемого обещания будет массивом, содержащим значения разрешения каждого из элементов в $promisesOrValues .
$ promise = React Promise race (iterable $ promisesOrValues );Инициирует соревновательную гонку, в которой остается один победитель. Возвращает обещание, которое разрешается таким же образом, как и первое выполненное обещание.
Возвращенное обещание станет бесконечно ожидающим , если $promisesOrValues содержит 0 элементов.
$ promise = React Promise any (iterable $ promisesOrValues ); Возвращает обещание, которое будет выполнено при разрешении любого из элементов в $promisesOrValues . Значение разрешения возвращенного обещания будет значением разрешения триггерного элемента.
Возвращенное обещание будет отклонено только в том случае, если все элементы в $promisesOrValues отклонены. Значением отклонения будет ReactPromiseExceptionCompositeException которое содержит все причины отклонения. Причины отклонения можно узнать с помощью CompositeException::getThrowables() .
Возвращенное обещание также будет отклонено с исключением ReactPromiseExceptionLengthException , если $promisesOrValues содержит 0 элементов.
React Promise set_rejection_handler(?callable $ callback ): ?callable;Устанавливает глобальный обработчик отклонения для необработанных отклонений обещаний.
Обратите внимание, что отклоненные обещания всегда должны обрабатываться аналогично тому, как любые исключения всегда должны перехватываться в блоке try + catch . Если вы удалите последнюю ссылку на отклоненное обещание, которое не было обработано, будет сообщено об отклонении необработанного обещания. См. также функцию reject() для получения более подробной информации.
Аргумент ?callable $callback ДОЛЖЕН быть допустимой функцией обратного вызова, которая принимает один аргумент Throwable или null значение для восстановления обработчика отклонения обещания по умолчанию. Возвращаемое значение функции обратного вызова будет игнорироваться и не будет иметь никакого эффекта, поэтому вам СЛЕДУЕТ вернуть void значение. Функция обратного вызова НЕ ДОЛЖНА вызывать выдачу, иначе программа будет завершена с фатальной ошибкой.
Функция возвращает предыдущий обработчик отклонения или null , если используется обработчик отклонения обещания по умолчанию.
Обработчик отклонения обещания по умолчанию зарегистрирует сообщение об ошибке, а также его трассировку стека:
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
React Promise reject ( new RuntimeException ( ' Unhandled ' )); Обработчик отклонения обещания может использоваться для настройки сообщения журнала или записи в пользовательские целевые объекты журнала. Как правило, эту функцию следует использовать только в крайнем случае, а отказы от обещаний лучше всего обрабатывать с помощью метода then() , метода catch() или метода finally() . См. также функцию reject() для получения более подробной информации.
function getAwesomeResultPromise ()
{
$ deferred = new React Promise Deferred ();
// Execute a Node.js-style function using the callback pattern
computeAwesomeResultAsynchronously ( function ( Throwable $ error , $ result ) use ( $ deferred ) {
if ( $ error ) {
$ deferred -> reject ( $ error );
} else {
$ deferred -> resolve ( $ result );
}
});
// Return the promise
return $ deferred -> promise ();
}
getAwesomeResultPromise ()
-> then (
function ( $ value ) {
// Deferred resolved, do something with $value
},
function ( Throwable $ reason ) {
// Deferred rejected, do something with $reason
}
);Несколько простых примеров, показывающих, как работает механика пересылки Promises/A. Конечно, эти примеры надуманны, и в реальном использовании цепочки обещаний обычно распределяются по нескольким вызовам функций или даже по нескольким уровням архитектуры вашего приложения.
Выполненные обещания пересылают значения разрешения следующему обещанию. Первое обещание, $deferred->promise() , будет разрешено со значением, переданным в $deferred->resolve() ниже.
Каждый вызов then() возвращает новое обещание, которое будет обрабатываться возвращаемым значением предыдущего обработчика. Это создает «конвейер» обещаний.
$ deferred = new React Promise Deferred ();
$ deferred -> promise ()
-> then ( function ( $ x ) {
// $x will be the value passed to $deferred->resolve() below
// and returns a *new promise* for $x + 1
return $ x + 1 ;
})
-> then ( function ( $ x ) {
// $x === 2
// This handler receives the return value of the
// previous handler.
return $ x + 1 ;
})
-> then ( function ( $ x ) {
// $x === 3
// This handler receives the return value of the
// previous handler.
return $ x + 1 ;
})
-> then ( function ( $ x ) {
// $x === 4
// This handler receives the return value of the
// previous handler.
echo ' Resolve ' . $ x ;
});
$ deferred -> resolve ( 1 ); // Prints "Resolve 4" Отклоненные промисы ведут себя аналогично, а также работают аналогично try/catch: когда вы перехватываете исключение, вы должны повторно сгенерировать его, чтобы оно распространилось.
Аналогичным образом, когда вы обрабатываете отклоненное обещание, чтобы распространить отклонение, «повторно выдайте» его, либо вернув отклоненное обещание, либо фактически выдав его (поскольку обещание преобразует выброшенные исключения в отклонения)
$ deferred = new React Promise Deferred ();
$ deferred -> promise ()
-> then ( function ( $ x ) {
throw new Exception ( $ x + 1 );
})
-> catch ( function ( Exception $ x ) {
// Propagate the rejection
throw $ x ;
})
-> catch ( function ( Exception $ x ) {
// Can also propagate by returning another rejection
return React Promise reject (
new Exception ( $ x -> getMessage () + 1 )
);
})
-> catch ( function ( $ x ) {
echo ' Reject ' . $ x -> getMessage (); // 3
});
$ deferred -> resolve ( 1 ); // Prints "Reject 3" Как и в случае с try/catch, вы можете выбрать, распространять или нет. Смешивание разрешений и отклонений по-прежнему будет передавать результаты обработчика предсказуемым образом.
$ deferred = new React Promise Deferred ();
$ deferred -> promise ()
-> then ( function ( $ x ) {
return $ x + 1 ;
})
-> then ( function ( $ x ) {
throw new Exception ( $ x + 1 );
})
-> catch ( function ( Exception $ x ) {
// Handle the rejection, and don't propagate.
// This is like catch without a rethrow
return $ x -> getMessage () + 1 ;
})
-> then ( function ( $ x ) {
echo ' Mixed ' . $ x ; // 4
});
$ deferred -> resolve ( 1 ); // Prints "Mixed 4" Рекомендуемый способ установки этой библиотеки — через Composer. Впервые в Composer?
Этот проект следует за SemVer. Это установит последнюю поддерживаемую версию из этой ветки:
composer require react/promise:^3.2См. также CHANGELOG для получения подробной информации об обновлениях версий.
Этот проект предназначен для работы на любой платформе и, следовательно, не требует каких-либо расширений PHP и поддерживает работу с PHP 7.1 до текущей версии PHP 8+. Для этого проекта настоятельно рекомендуется использовать последнюю поддерживаемую версию PHP .
Мы стремимся предоставить варианты долгосрочной поддержки (LTS) и обеспечить плавный путь обновления. Если вы используете более старую версию PHP, вы можете использовать ветку 2.x (PHP 5.4+) или 1.x (PHP 5.3+), которые предоставляют совместимый API, но не используют преимущества новых функций языка. Вы можете настроить одновременно несколько версий для поддержки более широкого диапазона версий PHP, например:
composer require " react/promise:^3 || ^2 || ^1 " Чтобы запустить набор тестов, вам сначала необходимо клонировать этот репозиторий, а затем установить все зависимости через Composer:
composer installЧтобы запустить набор тестов, перейдите в корень проекта и запустите:
vendor/bin/phpunitКроме того, мы используем PHPStan на максимальном уровне, чтобы обеспечить безопасность типов во всем проекте:
vendor/bin/phpstanPromise — это порт When.js, созданный Брайаном Кавальером.
Кроме того, большая часть документации была перенесена из Wiki-сайта When.js и документации API.
Выпущено по лицензии MIT.