

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 第 3 適用於 PHP 的 AWS SDK 版中的 Promise
<a name="guide_promises"></a>

 適用於 PHP 的 AWS SDK 使用 **promise 允許**非同步工作流程，而此非同步性允許同時傳送 HTTP 請求。由開發套件使用的 promise 規格是 [Promises/A\$1](https://promisesaplus.com/)。

## 什麼是承諾？
<a name="what-is-a-promise"></a>

*Promise* 代表非同步操作的最終結果。與 promise 主要互動的方式，是透過其 `then` 方法。此方法註冊回呼以接收 promise 的最終值或 promise 無法履行的理由。

 適用於 PHP 的 AWS SDK 倚賴 [guzzlehttp/promises](https://github.com/guzzle/promises) Composer 套件進行承諾實作。Guzzle promises 支援封鎖和非封鎖的工作流程，並可與任何非封鎖事件迴圈一起使用。

**注意**  
HTTP 請求會使用 適用於 PHP 的 AWS SDK 單一執行緒在 中同時傳送，其中使用非封鎖呼叫來傳輸一或多個 HTTP 請求，同時回應狀態變更 （例如履行或拒絕承諾）。

## 開發套件中的 Promise
<a name="promises-in-the-sdk"></a>

Promise 用於整個軟體開發套件。例如，promise 用於由開發套件所提供的大多數高階抽象概念：[分頁程式](guide_paginators.md#async-paginators)、[等待程式](guide_waiters.md#async-waiters)、[命令集區](guide_commands.md#command-pool)、[分段上傳](s3-multipart-upload.md)、[S3 目錄/儲存貯體傳輸](s3-transfer.md)等。

當您呼叫用任何 `Async` 非同步尾碼方法時，開發套件提供的所有用戶端都會傳回 promise。例如，下列程式碼示範如何建立取得 Amazon DynamoDB`DescribeTable` 操作結果的承諾。

```
$client = new Aws\DynamoDb\DynamoDbClient([
    'region'  => 'us-west-2',
    'version' => 'latest',
]);

// This will create a promise that will eventually contain a result
$promise = $client->describeTableAsync(['TableName' => 'mytable']);
```

請注意，您可以呼叫 `describeTable` 或 `describeTableAsync`。這些方法是由 API 模型和與用戶端關聯的 `__call` 編號支援的用戶端神奇 `version` 方法。透過呼叫不帶 `describeTable` 尾碼的 `Async` 方法，用戶端將在傳送 HTTP 請求時封鎖，並返回 `Aws\ResultInterface` 物件或擲出 `Aws\Exception\AwsException`。透過用 `Async` (即 `describeTableAsync`) 添加操作名稱尾碼，用戶端將建立一個最終由 `Aws\ResultInterface` 物件履行或被 `Aws\Exception\AwsException` 拒絕的 promise。

**重要**  
當 promise 傳回時，結果可能已經到達 (例如，當使用模擬處理常式時)，或者 HTTP 請求可能沒有啟動。

您可以使用 `then` 方法以 promise 註冊回呼。該方法接受兩個回呼，`$onFulfilled` 和 `$onRejected`，兩者都是非必要的。如果 promise 已履行，則會呼叫 `$onFulfilled` 回呼，如果 promise 被拒絕 (代表失敗)，則會呼叫 `$onRejected` 回呼。

```
$promise->then(
    function ($value) {
        echo "The promise was fulfilled with {$value}";
    },
    function ($reason) {
        echo "The promise was rejected with {$reason}";
    }
);
```

### 同時執行命令
<a name="executing-commands-concurrently"></a>

多個 promise 可以組合在一起，以便同時執行。這可以透過將軟體開發套件與非封鎖事件迴路整合，或透過建立多個 promise 並等待它們同時完成來實現。

```
use GuzzleHttp\Promise\Utils;

$sdk = new Aws\Sdk([
    'version' => 'latest',
    'region'  => 'us-east-1'
]);

$s3 = $sdk->createS3();
$ddb = $sdk->createDynamoDb();

$promises = [
    'buckets' => $s3->listBucketsAsync(),
    'tables'  => $ddb->listTablesAsync(),
];

// Wait for both promises to complete.
$results = Utils::unwrap($promises);

// Notice that this method will maintain the input array keys.
var_dump($results['buckets']->toArray());
var_dump($results['tables']->toArray());
```

**注意**  
[CommandPool](guide_commands.md#command-pool) 提供更強大的機制以同時執行多個 API 操作。

## 鏈結承諾
<a name="chaining-promises"></a>

Promise 的好處之一是它們可以組合，可讓您建立轉換管道。Promise 是由鏈結 `then` 回呼與後續 `then` 回呼構成。`then` 方法傳回的值是根據提供的回呼結果履行或拒絕的 promise。

```
$promise = $client->describeTableAsync(['TableName' => 'mytable']);

$promise
    ->then(
        function ($value) {
            $value['AddedAttribute'] = 'foo';
            return $value;
        },
        function ($reason) use ($client) {
            // The call failed. You can recover from the error here and
            // return a value that will be provided to the next successful
            // then() callback. Let's retry the call.
            return $client->describeTableAsync(['TableName' => 'mytable']);
        }
    )->then(
        function ($value) {
            // This is only invoked when the previous then callback is
            // fulfilled. If the previous callback returned a promise, then
            // this callback is invoked only after that promise is
            // fulfilled.
            echo $value['AddedAttribute']; // outputs "foo"
        },
        function ($reason) {
            // The previous callback was rejected (failed).
        }
    );
```

**注意**  
Promise 回呼的傳回值是提供給下游 promise 的 `$value` 引數。如果您要為下游 provide 鏈提供值，則必須在回呼函數中傳回一個值。

### 拒絕轉送
<a name="rejection-forwarding"></a>

promise 被拒絕時，您可以註冊一個回呼以呼叫。如果在任何回呼中擲出例外，promise 將被例外拒絕，且鏈中的下一個 promise 將被拒絕並產生例外。如果您從 `$onRejected` 回呼成功傳回一個值，則 promise 鏈中的下一個 promise 將使用來自 `$onRejected` 回呼傳回的值以履行。

## 等待承諾
<a name="waiting-on-promises"></a>

您可以透過使用 promise 的 `wait` 方法同步強制 promise 完成。

```
$promise = $client->listTablesAsync();
$result = $promise->wait();
```

如果在呼叫 promise 的 `wait` 函數時遇到例外，那麼 promise 會被例外拒絕，且會擲出該例外。

```
use Aws\Exception\AwsException;

$promise = $client->listTablesAsync();

try {
    $result = $promise->wait();
} catch (AwsException $e) {
    // Handle the error
}
```

在已履行的 promise 上呼叫 `wait` 不會觸發等待函數。它只會傳回之前傳送的值。

```
$promise = $client->listTablesAsync();
$result = $promise->wait();
assert($result ### $promise->wait());
```

在已拒絕的 promise 上呼叫 `wait` 會擲出例外。如果拒絕原因是 `\Exception` 的一個執行個體，則擲出原因。否則，會擲出 `GuzzleHttp\Promise\RejectionException`，並且可以透過呼叫例外的 `getReason` 方法來獲取原因。

**注意**  
中的 API 操作呼叫 適用於 PHP 的 AWS SDK 會遭到 `Aws\Exception\AwsException`類別的子類別拒絕。但是，交付給 `then` 方法的原因可能不同，因為增加了變更拒絕原因的自訂中介軟體。

## 取消承諾
<a name="canceling-promises"></a>

可以使用 promise 的 `cancel()` 方法取消 promise。如果 promise 已經解決，呼叫 `cancel()` 將不會生效。取消一個 promise 會取消該 promise，和任何等待該 promise 交付的 promise。一個遭到取消的 promise 會被 `GuzzleHttp\Promise\RejectionException` 拒絕。

## 結合承諾
<a name="combining-promises"></a>

您可以將 promise 結合到彙整 promise 中以建構更複雜的工作流程。`guzzlehttp/promise` 套件包含各種可用於結合 promise 的函數。

您可以在 [namespace-GuzzleHttp.Promise](https://docs.aws.amazon.com/aws-sdk-php/v3/api/namespace-GuzzleHttp.Promise.html) 上找到所有 promise 集合函數的 API 文件。

### each 和 each\$1limit
<a name="each-and-each-limit"></a>

當您有`Aws\CommandInterface`命令的任務佇列可與固定集區大小同時執行時，請使用 [CommandPool](guide_commands.md#command-pool) （命令可以位於記憶體中或由延遲迭代器產生）。`CommandPool` 確保同時傳送固定數量的命令，直到提供的反覆運算器耗盡為止。

`CommandPool` 僅適用於由同一用戶端執行的命令。您可以使用 `GuzzleHttp\Promise\each_limit` 函數，使用固定的集區大小同時執行不同用戶端的傳送命令。

```
use GuzzleHttp\Promise;

$sdk = new Aws\Sdk([
    'version' => 'latest',
    'region'  => 'us-west-2'
]);

$s3 = $sdk->createS3();
$ddb = $sdk->createDynamoDb();

// Create a generator that yields promises
$promiseGenerator = function () use ($s3, $ddb) {
    yield $s3->listBucketsAsync();
    yield $ddb->listTablesAsync();
    // yield other promises as needed...
};

// Execute the tasks yielded by the generator concurrently while limiting the
// maximum number of concurrent promises to 5
$promise = Promise\each_limit($promiseGenerator(), 5);

// Waiting on an EachPromise will wait on the entire task queue to complete
$promise->wait();
```

### Promise coroutines
<a name="promise-coroutines"></a>

Guzzle promise 程式庫一個更強大的功能，是它允許您使用 promise coroutine，讓編寫非同步工作流看起來更像是編寫傳統的同步工作流。實際上， 適用於 PHP 的 AWS SDK 在大多數高層級抽象中使用 coroutine promise。

假設在儲存貯體可用時，您希望建立多個儲存貯體並上傳檔案到儲存貯體中，且想同時完成這些事，以便能夠盡快發生。您可以透過使用 `all()` promise 函數將多個 coroutine promise 組合在一起，輕鬆完成此任務。

```
use GuzzleHttp\Promise;

$uploadFn = function ($bucket) use ($s3Client) {
    return Promise\coroutine(function () use ($bucket, $s3Client) {
        // You can capture the result by yielding inside of parens
        $result = (yield $s3Client->createBucket(['Bucket' => $bucket]));
        // Wait on the bucket to be available
        $waiter = $s3Client->getWaiter('BucketExists', ['Bucket' => $bucket]);
        // Wait until the bucket exists
        yield $waiter->promise();
        // Upload a file to the bucket
        yield $s3Client->putObjectAsync([
            'Bucket' => $bucket,
            'Key'    => '_placeholder',
            'Body'   => 'Hi!'
        ]);
    });
};

// Create the following buckets
$buckets = ['amzn-s3-demo-bucket1', 'amzn-s3-demo-bucket2', 'amzn-s3-demo-bucket3'];
$promises = [];

// Build an array of promises
foreach ($buckets as $bucket) {
    $promises[] = $uploadFn($bucket);
}

// Aggregate the promises into a single "all" promise
$aggregate = Promise\all($promises);

// You can then() off of this promise or synchronously wait
$aggregate->wait();
```