Built-in waiters for polling operations and utility functions for common S3 tasks, providing convenient abstractions for asynchronous operations.
Wait for bucket state changes with automatic polling and timeout handling.
/**
* Wait until a bucket exists and is accessible
*/
function waitUntilBucketExists(
params: WaiterConfiguration<S3Client>,
input: HeadBucketCommandInput
): Promise<WaiterResult>;
/**
* Wait until a bucket is deleted or inaccessible
*/
function waitUntilBucketNotExists(
params: WaiterConfiguration<S3Client>,
input: HeadBucketCommandInput
): Promise<WaiterResult>;
interface WaiterConfiguration<Client> {
/** S3 client instance */
client: Client;
/** Maximum wait time in seconds (default: 120) */
maxWaitTime?: number;
/** Minimum delay between checks in seconds (default: 2) */
minDelay?: number;
/** Maximum delay between checks in seconds (default: 120) */
maxDelay?: number;
}
interface WaiterResult {
/** Final state */
state: WaiterState;
/** Reason for the final state */
reason?: any;
}
type WaiterState = "SUCCESS" | "FAILURE" | "RETRY" | "TIMEOUT" | "ABORTED";Usage Examples:
import {
S3Client,
CreateBucketCommand,
DeleteBucketCommand,
waitUntilBucketExists,
waitUntilBucketNotExists
} from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-east-1" });
// Wait for bucket creation to complete
async function createAndWaitForBucket(bucketName: string) {
// Create the bucket
const createBucket = new CreateBucketCommand({
Bucket: bucketName
});
await client.send(createBucket);
// Wait for it to be accessible
const waiterResult = await waitUntilBucketExists(
{
client,
maxWaitTime: 60, // Wait up to 60 seconds
minDelay: 2, // Check every 2 seconds
maxDelay: 10 // Max 10 seconds between checks
},
{ Bucket: bucketName }
);
if (waiterResult.state === "SUCCESS") {
console.log(`Bucket ${bucketName} is ready`);
} else {
throw new Error(`Bucket creation failed: ${waiterResult.reason}`);
}
}
// Wait for bucket deletion to complete
async function deleteAndWaitForBucket(bucketName: string) {
// Delete the bucket
const deleteBucket = new DeleteBucketCommand({
Bucket: bucketName
});
await client.send(deleteBucket);
// Wait for it to be gone
const waiterResult = await waitUntilBucketNotExists(
{ client, maxWaitTime: 30 },
{ Bucket: bucketName }
);
if (waiterResult.state === "SUCCESS") {
console.log(`Bucket ${bucketName} has been deleted`);
} else {
throw new Error(`Bucket deletion incomplete: ${waiterResult.reason}`);
}
}
// Usage
await createAndWaitForBucket("my-new-bucket");
await deleteAndWaitForBucket("my-old-bucket");Wait for object state changes with automatic polling and timeout handling.
/**
* Wait until an object exists in S3
*/
function waitUntilObjectExists(
params: WaiterConfiguration<S3Client>,
input: HeadObjectCommandInput
): Promise<WaiterResult>;
/**
* Wait until an object is deleted from S3
*/
function waitUntilObjectNotExists(
params: WaiterConfiguration<S3Client>,
input: HeadObjectCommandInput
): Promise<WaiterResult>;Usage Examples:
import {
S3Client,
PutObjectCommand,
DeleteObjectCommand,
waitUntilObjectExists,
waitUntilObjectNotExists
} from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-east-1" });
// Wait for object upload to be complete and accessible
async function uploadAndWaitForObject(bucket: string, key: string, content: string) {
// Upload the object
const putObject = new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: content
});
await client.send(putObject);
// Wait for it to be accessible
const waiterResult = await waitUntilObjectExists(
{
client,
maxWaitTime: 30,
minDelay: 1,
maxDelay: 5
},
{
Bucket: bucket,
Key: key
}
);
if (waiterResult.state === "SUCCESS") {
console.log(`Object ${key} is ready`);
} else {
throw new Error(`Object upload failed: ${waiterResult.reason}`);
}
}
// Wait for object deletion to complete
async function deleteAndWaitForObject(bucket: string, key: string) {
// Delete the object
const deleteObject = new DeleteObjectCommand({
Bucket: bucket,
Key: key
});
await client.send(deleteObject);
// Wait for it to be gone
const waiterResult = await waitUntilObjectNotExists(
{ client, maxWaitTime: 15 },
{
Bucket: bucket,
Key: key
}
);
if (waiterResult.state === "SUCCESS") {
console.log(`Object ${key} has been deleted`);
} else {
throw new Error(`Object deletion incomplete: ${waiterResult.reason}`);
}
}
// Usage
await uploadAndWaitForObject("my-bucket", "test-file.txt", "Hello, World!");
await deleteAndWaitForObject("my-bucket", "old-file.txt");Handle different waiter outcomes and implement custom retry logic.
// Advanced waiter configuration with custom behavior
interface AdvancedWaiterConfiguration<Client> extends WaiterConfiguration<Client> {
/** Custom acceptor functions */
acceptors?: WaiterAcceptor[];
/** Abort signal for cancellation */
abortSignal?: AbortSignal;
}
interface WaiterAcceptor {
/** State to return when this acceptor matches */
state: WaiterState;
/** Matcher function */
matcher: (result: any) => boolean;
/** Expected value to match against */
expected?: any;
}Advanced Waiter Usage:
import {
S3Client,
waitUntilObjectExists,
WaiterState
} from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-east-1" });
// Wait with timeout and error handling
async function waitForObjectWithRetry(
bucket: string,
key: string,
maxRetries: number = 3
): Promise<boolean> {
let attempt = 0;
while (attempt < maxRetries) {
try {
const result = await waitUntilObjectExists(
{
client,
maxWaitTime: 30,
minDelay: 2,
maxDelay: 8
},
{ Bucket: bucket, Key: key }
);
switch (result.state) {
case "SUCCESS":
return true;
case "TIMEOUT":
console.warn(`Attempt ${attempt + 1}: Timeout waiting for object`);
break;
case "FAILURE":
console.error(`Attempt ${attempt + 1}: Failed - ${result.reason}`);
break;
default:
console.warn(`Attempt ${attempt + 1}: Unexpected state - ${result.state}`);
}
} catch (error) {
console.error(`Attempt ${attempt + 1}: Error - ${error.message}`);
}
attempt++;
if (attempt < maxRetries) {
// Exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return false;
}
// Wait with cancellation support
async function waitForObjectWithCancellation(
bucket: string,
key: string,
timeoutMs: number = 30000
): Promise<boolean> {
const abortController = new AbortController();
// Set up timeout
const timeoutId = setTimeout(() => {
abortController.abort();
}, timeoutMs);
try {
const result = await waitForObjectExists(
{
client,
maxWaitTime: Math.ceil(timeoutMs / 1000),
abortSignal: abortController.signal
},
{ Bucket: bucket, Key: key }
);
clearTimeout(timeoutId);
return result.state === "SUCCESS";
} catch (error) {
clearTimeout(timeoutId);
if (error.name === "AbortError") {
console.log("Wait operation was cancelled");
return false;
}
throw error;
}
}
// Usage examples
const exists = await waitForObjectWithRetry("my-bucket", "important-file.txt", 5);
console.log(`Object exists after retries: ${exists}`);
const existsWithTimeout = await waitForObjectWithCancellation(
"my-bucket",
"slow-upload.dat",
15000
);
console.log(`Object exists within timeout: ${existsWithTimeout}`);Common utility patterns for S3 operations.
// Utility types for common patterns
interface S3ObjectReference {
Bucket: string;
Key: string;
VersionId?: string;
}
interface S3OperationOptions {
/** Maximum retry attempts */
maxRetries?: number;
/** Delay between retries (ms) */
retryDelay?: number;
/** Exponential backoff multiplier */
backoffMultiplier?: number;
/** Request timeout (ms) */
timeout?: number;
}Utility Function Examples:
import {
S3Client,
HeadObjectCommand,
GetObjectCommand,
PutObjectCommand,
CopyObjectCommand,
waitForObjectExists
} from "@aws-sdk/client-s3";
const client = new S3Client({ region: "us-east-1" });
// Utility: Check if object exists
async function objectExists(bucket: string, key: string): Promise<boolean> {
try {
await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
return true;
} catch (error) {
if (error.name === "NotFound") {
return false;
}
throw error;
}
}
// Utility: Safe object upload with existence check
async function safeUploadObject(
bucket: string,
key: string,
content: string | Buffer,
options: { overwrite?: boolean } = {}
): Promise<string> {
// Check if object already exists
const exists = await objectExists(bucket, key);
if (exists && !options.overwrite) {
throw new Error(`Object ${key} already exists. Set overwrite=true to replace.`);
}
// Upload the object
const putCommand = new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: content,
ContentType: typeof content === "string" ? "text/plain" : "application/octet-stream"
});
const response = await client.send(putCommand);
// Wait for it to be accessible
await waitForObjectExists(
{ client, maxWaitTime: 30 },
{ Bucket: bucket, Key: key }
);
return response.ETag || "";
}
// Utility: Download object as string
async function downloadObjectAsString(
bucket: string,
key: string
): Promise<string> {
const getCommand = new GetObjectCommand({
Bucket: bucket,
Key: key
});
const response = await client.send(getCommand);
if (!response.Body) {
throw new Error("Object has no content");
}
return await response.Body.transformToString();
}
// Utility: Copy object with wait
async function copyObjectWithWait(
sourceBucket: string,
sourceKey: string,
destBucket: string,
destKey: string
): Promise<void> {
const copyCommand = new CopyObjectCommand({
Bucket: destBucket,
Key: destKey,
CopySource: `${sourceBucket}/${sourceKey}`
});
await client.send(copyCommand);
// Wait for the copy to be accessible
await waitForObjectExists(
{ client, maxWaitTime: 60 },
{ Bucket: destBucket, Key: destKey }
);
}
// Utility: Batch operation with concurrency control
async function batchOperation<T, R>(
items: T[],
operation: (item: T) => Promise<R>,
concurrency: number = 5
): Promise<R[]> {
const results: R[] = [];
const executing: Promise<void>[] = [];
for (const item of items) {
const promise = operation(item).then(result => {
results.push(result);
});
executing.push(promise);
if (executing.length >= concurrency) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
await Promise.all(executing);
return results;
}
// Usage examples
const exists = await objectExists("my-bucket", "test-file.txt");
console.log(`Object exists: ${exists}`);
const etag = await safeUploadObject(
"my-bucket",
"new-file.txt",
"Hello, World!",
{ overwrite: false }
);
console.log(`Uploaded with ETag: ${etag}`);
const content = await downloadObjectAsString("my-bucket", "text-file.txt");
console.log(`Downloaded content: ${content}`);
await copyObjectWithWait(
"source-bucket", "original.txt",
"dest-bucket", "copy.txt"
);
console.log("Copy completed and verified");
// Batch upload multiple files
const uploadTasks = [
{ key: "file1.txt", content: "Content 1" },
{ key: "file2.txt", content: "Content 2" },
{ key: "file3.txt", content: "Content 3" }
];
const uploadResults = await batchOperation(
uploadTasks,
async (task) => {
return await safeUploadObject("my-bucket", task.key, task.content);
},
3 // Max 3 concurrent uploads
);
console.log(`Completed ${uploadResults.length} uploads`);Custom polling utilities for complex scenarios.
// Generic polling utility
async function pollUntil<T>(
operation: () => Promise<T>,
condition: (result: T) => boolean,
options: {
maxAttempts?: number;
interval?: number;
backoff?: boolean;
timeout?: number;
} = {}
): Promise<T> {
const {
maxAttempts = 30,
interval = 1000,
backoff = false,
timeout = 0
} = options;
const startTime = Date.now();
let attempt = 0;
let currentInterval = interval;
while (attempt < maxAttempts) {
if (timeout > 0 && Date.now() - startTime > timeout) {
throw new Error("Polling timeout exceeded");
}
try {
const result = await operation();
if (condition(result)) {
return result;
}
} catch (error) {
console.warn(`Polling attempt ${attempt + 1} failed:`, error.message);
}
attempt++;
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, currentInterval));
if (backoff) {
currentInterval = Math.min(currentInterval * 1.5, 10000);
}
}
}
throw new Error(`Polling failed after ${maxAttempts} attempts`);
}
// Usage: Wait for object to have specific size
async function waitForObjectSize(
bucket: string,
key: string,
expectedSize: number
): Promise<void> {
await pollUntil(
async () => {
const headResponse = await client.send(new HeadObjectCommand({
Bucket: bucket,
Key: key
}));
return headResponse.ContentLength || 0;
},
(size) => size === expectedSize,
{
maxAttempts: 20,
interval: 2000,
backoff: true,
timeout: 60000
}
);
}
// Usage: Wait for multipart upload completion
async function waitForMultipartUploadCompletion(
bucket: string,
key: string,
uploadId: string
): Promise<boolean> {
try {
await pollUntil(
async () => {
return await objectExists(bucket, key);
},
(exists) => exists,
{
maxAttempts: 30,
interval: 3000,
timeout: 300000 // 5 minutes
}
);
return true;
} catch (error) {
console.error("Multipart upload did not complete:", error.message);
return false;
}
}