Outcomes and errors
What hook actions return, how callbacks run, and how to read errors and aborts.
Hook actions do not throw for transfer errors. Each one resolves with an outcome for one key, or a result for a batch. Read the status, then the data or the error.
Outcomes
remove, download, and presign resolve with an outcome:
type OperationOutcome<TData> =
| { status: "success"; key: string; data: TData }
| { status: "error"; key: string; error: APIError }
| { status: "blocked"; key: string }
| { status: "aborted"; key: string };success—dataholds the response.error—erroris anAPIError.blocked— abefore*callback returnedfalse.aborted— the call was cancelled. A delete has no cancel, so it never ends this way.
const outcome = await download(objectKey);
if (outcome.status === "error") {
console.log(outcome.key, outcome.error.code);
}Results
Batch actions resolve with one result that sorts every unit:
| Action | Result |
|---|---|
useUpload().handleFiles | { uploaded, failed, blocked, aborted } by file |
useDelete().removeMany | { deleted, failed, blocked } by key |
useDownload().presignMany | { signed, failed, blocked } by key |
failed entries pair the unit with its APIError, such as
{ key, error }.
Joined calls
The download and delete hooks track each key on its own. A second call for a key that is already running does not start a new request. It joins the running call and resolves with the same outcome, and the callbacks do not run a second time.
Callback order
Every hook runs its callbacks the same way:
before*(beforeUpload,beforeDownload,beforeDelete) runs once per call. Returningfalseblocks the call. A throw fails it withHOOK_FAILED.on*Start(onUploadStart,onDeleteStart,onDownloadStart) runs once per call. A throw fails it withHOOK_FAILED.onErrorruns once per failed unit, thenonSuccessonce per successful unit.onSettledruns once per call, last.
onSuccess, onSettled, and the other after-hooks are awaited. If one throws,
the error goes to the global reportError and the outcome does not change.
Errors
Every error is an APIError from @dimah-s3/core:
code— always set. Branch on it withisS3ErrorCode(error, code).params— values for the message, such as{ size, maxFileSize }.statusCode— the HTTP status.message— English. Show usersuseFormatDimahError()instead; see Helpers.
Server codes come from the route and your hooks. Some codes are raised in the browser only:
| Code | When |
|---|---|
TOO_MANY_FILES | A call has more valid files than maxFiles. |
HOOK_FAILED | A before* or on*Start callback threw. |
NETWORK_ERROR | A request got no response. |
UPLOAD_FAILED | S3 rejected an upload. params.status is S3's status. |
DOWNLOAD_FAILED | A fetch download got a non-2xx response. |
PARTIAL_FAILURE | upload.error when more than one file failed. |
UNKNOWN_ERROR | Anything that is not an APIError. The original is cause. |
The full table is in Errors.
import { isS3ErrorCode } from "@dimah-s3/core";
const { failed } = await upload.handleFiles(files);
for (const { file, error } of failed) {
if (isS3ErrorCode(error, "FILE_TOO_LARGE")) {
console.log(`${file.name} is too large`);
}
}Aborts
A cancel is not an error. Hooks report it as an aborted outcome, or as the
aborted list of an upload result.
Engine functions such as uploadFile reject with the signal's reason instead.
The upload abort reasons are DOMExceptions named AbortError. Read which
kind with getUploadAbortKind:
import { getUploadAbortKind } from "@dimah-s3/react/engine";
try {
await uploadFile(api, file, options);
} catch (error) {
const kind = getUploadAbortKind(error); // "cancel", "detach", or null
if (kind === null) throw error;
}See Engine.
Type reference
Prop
Type
Prop
Type