Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
A function to convert the thrown value into your error type
TE.tryCatch(
() => somePromise, // The async work(thrown) =>toError(thrown) // Convert failures to your error type
)
Creating Success and Failure Values
// Wrap a value as successconst success = TE.right<Error, number>(42)
// Wrap a value as failureconst failure = TE.left<Error, number>(newError('Nope'))
// From a nullable value (null/undefined becomes error)const fromNullable = TE.fromNullable(newError('Value was null'))
const result = fromNullable(maybeUser) // TaskEither<Error, User>// From a conditionconst mustBePositive = TE.fromPredicate(
(n: number) => n > 0,
(n) =>newError(`Expected positive, got ${n}`)
)
2. Chaining Async Operations
The Problem: Callback Hell / Nested Awaits
// BEFORE: Deeply nested, hard to followasyncfunctionprocessOrder(orderId: string) {
try {
const order = awaitfetchOrder(orderId)
if (!order) thrownewError('Order not found')
try {
const user = awaitfetchUser(order.userId)
if (!user) thrownewError('User not found')
try {
const inventory = awaitcheckInventory(order.items)
if (!inventory.available) thrownewError('Out of stock')
try {
const payment = awaitchargePayment(user, order.total)
if (!payment.success) thrownewError('Payment failed')
try {
const shipment = awaitcreateShipment(order, user)
return { order, shipment, payment }
} catch (e) {
// Refund payment? Log? What's the state now?awaitrefundPayment(payment.id)
throw e
}
} catch (e) {
throw e
}
} catch (e) {
throw e
}
} catch (e) {
throw e
}
} catch (e) {
console.error('Order processing failed', e)
throw e
}
}
When each operation depends on the previous result
When you need to respect rate limits
When order matters
Parallel (all at once):
When operations are independent
When you want speed
When fetching multiple resources by ID
Sequential Chaining
// Operations depend on each other - must be sequentialconstgetUserWithOrg = (userId: string) =>
pipe(
fetchUser(userId), // First: get userTE.chain(user =>fetchTeam(user.teamId)), // Then: get their teamTE.chain(team =>fetchOrganization(team.orgId)) // Finally: get org
)
// Fetch multiple users in parallelconst userIds = ['1', '2', '3', '4', '5']
// TE.traverseArray runs all fetches in parallelconst fetchAllUsers = pipe(
userIds,
TE.traverseArray(fetchUser)
) // TaskEither<Error, readonly User[]>// Note: Fails fast - if ANY request fails, the whole thing fails// All errors after the first are lost
Parallel with Batch Control
When you need to limit concurrent requests:
const chunk = <T>(arr: T[], size: number): T[][] => {
constchunks: T[][] = []
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size))
}
return chunks
}
// Process in batches of 5 concurrent requestsconstfetchUsersWithLimit = (userIds: string[]) => {
const batches = chunk(userIds, 5)
returnpipe(
batches,
// Process batches sequentiallyTE.traverseArray(batch =>// But within each batch, run in parallelpipe(batch, TE.traverseArray(fetchUser))
),
TE.map(results => results.flat())
)
}
Sequential When Parallel Looks Tempting
// WRONG: This looks parallel but order might matter for DB operationsconstcreateUserAndProfile = (userData: UserData) =>
sequenceT(TE.ApplyPar)(
createUser(userData), // Creates user with IDcreateProfile(userData.profile) // Needs user ID - race condition!
)
// RIGHT: Sequential when there's a dependencyconstcreateUserAndProfile = (userData: UserData) =>
pipe(
createUser(userData),
TE.chain(user =>pipe(
createProfile(user.id, userData.profile),
TE.map(profile => ({ user, profile }))
)
)
)
// fold: Handle both success and failure, returns a Task (no more error channel)const displayResult = pipe(
fetchUser(userId),
TE.fold(
(error) => T.of(`Error: ${error.message}`),
(user) => T.of(`Welcome, ${user.name}!`)
)
) // Task<string>// Execute and get the stringconst message = awaitdisplayResult()
Getting the Raw Either
// Sometimes you need to work with the Either directlyconst result = awaitfetchUser(userId)() // Either<Error, User>if (E.isLeft(result)) {
console.error('Failed:', result.left)
} else {
console.log('User:', result.right)
}
// Transform success valueTE.map(user => user.name)
// Transform errorTE.mapLeft(error => ({ ...error, timestamp: Date.now() }))
// Transform both at onceTE.bimap(
error =>enhanceError(error),
user => user.profile
)
Filtering
// Fail if condition not metpipe(
fetchUser(userId),
TE.filterOrElse(
user => user.isActive,
user =>newError(`User ${user.id} is not active`)
)
)
Side Effects Without Changing Value
// Log on success, keep the value unchangedpipe(
fetchUser(userId),
TE.tap(user =>TE.fromIO(() =>console.log(`Fetched user: ${user.id}`)))
)
// Log on error, keep the error unchangedpipe(
fetchUser(userId),
TE.tapError(error =>TE.fromIO(() =>console.error(`Failed: ${error.message}`)))
)
// chainFirst is like tap but for operations that return TaskEitherpipe(
createUser(userData),
TE.chainFirst(user =>sendWelcomeEmail(user.email))
) // Returns the created user, not the email result
Converting From Other Types
// From Eitherconst fromEither = TE.fromEither(E.right(42))
// From Optionimport * as O from'fp-ts/Option'const fromOption = TE.fromOption(() =>newError('Value was None'))
const result = fromOption(O.some(42))
// From booleanconst fromBoolean = TE.fromPredicate(
(x: number) => x > 0,
() =>newError('Must be positive')
)