
Codificar falla en su programa.
Este paquete contiene un tipo de Result que representa el éxito ( Ok ) o la falla ( Err ).
Para las tareas asincrónicas, neverthrow ofrece una clase ResultAsync que envuelve una Promise<Result<T, E>> y le brinda el mismo nivel de expresividad y control como Result<T, E> .
ResultAsync es thenable lo que significa que se comporta exactamente como una Promise<Result> ... excepto que tiene acceso a los mismos métodos que Result proporciona sin tener que await o .then la promesa! Consulte el wiki para ver ejemplos y mejores prácticas.
¿Necesita ver ejemplos de la vida real de cómo aprovechar este paquete para el manejo de errores? Vea este repositorio: https://github.com/parlez-vous/server
eslint-plugin-neverthrowResult )okerrResult.isOk (método)Result.isErr (método)Result.map (método)Result.mapErr (método)Result.unwrapOr (método)Result.andThen .Result.asyncAndThen (método)Result.orElse (método)Result.match (método)Result.asyncMap (método)Result.andTee (método)Result.andThrough (método)Result.asyncAndThrough (método)Result.fromThrowable (método de clase estática)Result.combine (método de clase estática)Result.combineWithAllErrors (método de clase estática)Result.safeUnwrap()ResultAsync )okAsyncerrAsyncResultAsync.fromThrowable (método de clase estática)ResultAsync.fromPromise (método de clase estática)ResultAsync.fromSafePromise (método de clase estática)ResultAsync.map (método)ResultAsync.mapErr (método)ResultAsync.unwrapOr (método)ResultAsync.andThen (método)ResultAsync.orElse (método)ResultAsync.match (método)ResultAsync.andTee (método)ResultAsync.andThrough (método)ResultAsync.combine (método de clase estática)ResultAsync.combineWithAllErrors (método de clase estática)ResultAsync.safeUnwrap()fromThrowablefromAsyncThrowablefromPromisefromSafePromisesafeTry > npm install neverthroweslint-plugin-neverthrow Como parte del programa de recompensas de neverthrow , el usuario MdBetancourt creó eslint-plugin-neverthrow para garantizar que los errores no sean controlados.
Instalar ejecutando:
> npm install eslint-plugin-neverthrow Con eslint-plugin-neverthrow , se ve obligado a consumir el resultado en una de las siguientes tres maneras:
.match.unwrapOr._unsafeUnwrap Esto asegura que esté manejando explícitamente el error de su Result .
Este complemento es esencialmente una portada del atributo must-use de Rust.
neverthrow expone lo siguiente:
ok Función de conveniencia para crear una variante de Result Okerr para crear una variante Err de ResultOk clase y tipoErr y escribaResult , así como espacio de nombres / objeto desde el cual llamar Result.fromThrowable .ResultAsyncokAsync para crear un ResultAsync que contenga un Result de tipo OkerrAsync para crear un ResultAsync que contiene un Result de tipo Err import {
ok ,
Ok ,
err ,
Err ,
Result ,
okAsync ,
errAsync ,
ResultAsync ,
fromAsyncThrowable ,
fromThrowable ,
fromPromise ,
fromSafePromise ,
safeTry ,
} from 'neverthrow' Echa un vistazo a la wiki para obtener ayuda sobre cómo aprovechar al máximo el neverthrow .
Si encuentra útil este paquete, considere patrocinarme o simplemente comprarme un café.
Result ) ok Construye una variante de Result Ok
Firma:
ok < T , E > ( value : T ) : Ok < T , E > { ... }Ejemplo:
import { ok } from 'neverthrow'
const myResult = ok ( { myData : 'test' } ) // instance of `Ok`
myResult . isOk ( ) // true
myResult . isErr ( ) // false⬆️ Volver a arriba
err Construye una variante Err de Result
Firma:
err < T , E > ( error : E ) : Err < T , E > { ... }Ejemplo:
import { err } from 'neverthrow'
const myResult = err ( 'Oh noooo' ) // instance of `Err`
myResult . isOk ( ) // false
myResult . isErr ( ) // true⬆️ Volver a arriba
Result.isOk (método) Devuelve true si el resultado es una variante Ok
Firma:
isOk ( ) : boolean { ... }⬆️ Volver a arriba
Result.isErr (método) Devuelve true si el resultado es una variante Err
Firma :
isErr ( ) : boolean { ... }⬆️ Volver a arriba
Result.map (método) Mapea un Result<T, E> para Result<U, E> aplicando una función a un valor Ok contenido, dejando un valor Err .
Esta función se puede utilizar para componer los resultados de dos funciones.
Firma:
class Result < T , E > {
map < U > ( callback : ( value : T ) => U ) : Result < U , E > { ... }
}Ejemplo :
import { getLines } from 'imaginary-parser'
// ^ assume getLines has the following signature:
// getLines(str: string): Result<Array<string>, Error>
// since the formatting is deemed correct by `getLines`
// then it means that `linesResult` is an Ok
// containing an Array of strings for each line of code
const linesResult = getLines ( '1n2n3n4n' )
// this Result now has a Array<number> inside it
const newResult = linesResult . map (
( arr : Array < string > ) => arr . map ( parseInt )
)
newResult . isOk ( ) // true⬆️ Volver a arriba
Result.mapErr (método) Mapea un Result<T, E> para Result<T, F> aplicando una función a un valor Err contenido, dejando un valor Ok intacto.
Esta función se puede usar para pasar por un resultado exitoso mientras maneja un error.
Firma:
class Result < T , E > {
mapErr < F > ( callback : ( error : E ) => F ) : Result < T , F > { ... }
}Ejemplo :
import { parseHeaders } from 'imaginary-http-parser'
// imagine that parseHeaders has the following signature:
// parseHeaders(raw: string): Result<SomeKeyValueMap, ParseError>
const rawHeaders = 'nonsensical gibberish and badly formatted stuff'
const parseResult = parseHeaders ( rawHeaders )
parseResult . mapErr ( parseError => {
res . status ( 400 ) . json ( {
error : parseError
} )
} )
parseResult . isErr ( ) // true⬆️ Volver a arriba
Result.unwrapOr (método) Descansa el valor Ok o devuelve el valor predeterminado si hay un Err
Firma:
class Result < T , E > {
unwrapOr < T > ( value : T ) : T { ... }
}Ejemplo :
const myResult = err ( 'Oh noooo' )
const multiply = ( value : number ) : number => value * 2
const unwrapped : number = myResult . map ( multiply ) . unwrapOr ( 10 )⬆️ Volver a arriba
Result.andThen . Misma idea que map de arriba. Excepto que debe devolver un nuevo Result .
El valor devuelto será un Result . A partir de v4.1.0-beta , puede devolver distintos tipos de error (ver firma a continuación). Antes de v4.1.0-beta , el tipo de error no podría ser distinto.
Esto es útil para cuando necesita hacer un cálculo posterior utilizando el valor T interno, pero ese cálculo puede fallar.
Además, andThen es realmente útil como herramienta para aplanar un Result<Result<A, E2>, E1> en un Result<A, E2> (ver ejemplo a continuación).
Firma:
class Result < T , E > {
// Note that the latest version lets you return distinct errors as well.
// If the error types (E and F) are the same (like `string | string`)
// then they will be merged into one type (`string`)
andThen < U , F > (
callback : ( value : T ) => Result < U , F >
) : Result < U , E | F > { ... }
}Ejemplo 1: Resultados de encadenamiento
import { err , ok } from 'neverthrow'
const sq = ( n : number ) : Result < number , number > => ok ( n ** 2 )
ok ( 2 )
. andThen ( sq )
. andThen ( sq ) // Ok(16)
ok ( 2 )
. andThen ( sq )
. andThen ( err ) // Err(4)
ok ( 2 )
. andThen ( err )
. andThen ( sq ) // Err(2)
err ( 3 )
. andThen ( sq )
. andThen ( sq ) // Err(3)Ejemplo 2: Resultados anidados de aplanamiento
// It's common to have nested Results
const nested = ok ( ok ( 1234 ) )
// notNested is a Ok(1234)
const notNested = nested . andThen ( ( innerResult ) => innerResult )⬆️ Volver a arriba
Result.asyncAndThen (método) La misma idea que andThen arriba, excepto que debe devolver un nuevo ResultAsync .
El valor devuelto será un ResultAsync .
Firma:
class Result < T , E > {
asyncAndThen < U , F > (
callback : ( value : T ) => ResultAsync < U , F >
) : ResultAsync < U , E | F > { ... }
}⬆️ Volver a arriba
Result.orElse (método) Toma un valor Err y lo mapea a un Result<T, SomeNewType> . Esto es útil para la recuperación de errores.
Firma:
class Result < T , E > {
orElse < U , A > (
callback : ( error : E ) => Result < U , A >
) : Result < U | T , A > { ... }
}Ejemplo:
enum DatabaseError {
PoolExhausted = 'PoolExhausted' ,
NotFound = 'NotFound' ,
}
const dbQueryResult : Result < string , DatabaseError > = err ( DatabaseError . NotFound )
const updatedQueryResult = dbQueryResult . orElse ( ( dbError ) =>
dbError === DatabaseError . NotFound
? ok ( 'User does not exist' ) // error recovery branch: ok() must be called with a value of type string
//
//
// err() can be called with a value of any new type that you want
// it could also be called with the same error value
//
// err(dbError)
: err ( 500 )
)⬆️ Volver a arriba
Result.match (método) Dadas 2 funciones (una para la variante Ok y otra para la variante Err ) ejecuta la función que coincide con la variante Result .
Las devoluciones de llamada de coincidencias no requieren devolver un Result , sin embargo, puede devolver un Result si lo desea.
Firma:
class Result < T , E > {
match < A , B = A > (
okCallback : ( value : T ) => A ,
errorCallback : ( error : E ) => B
) : A | B => { ... }
} match es como map de encadenamiento y mapErr , con la distinción de que con match ambas funciones deben tener el mismo tipo de retorno. Las diferencias entre match y map de encadenamiento y mapErr son que:
match de ambas funciones debe tener el mismo tipo de retorno Amatch desenvuelve el Result<T, E> en una A (el tipo de retorno de las funciones de coincidencia)Ejemplo:
// map/mapErr api
// note that you DON'T have to append mapErr
// after map which means that you are not required to do
// error handling
computationThatMightFail ( ) . map ( console . log ) . mapErr ( console . error )
// match api
// works exactly the same as above since both callbacks
// only perform side effects,
// except, now you HAVE to do error handling :)
computationThatMightFail ( ) . match ( console . log , console . error )
// Returning values
const attempt = computationThatMightFail ( )
. map ( ( str ) => str . toUpperCase ( ) )
. mapErr ( ( err ) => `Error: ${ err } ` )
// `attempt` is of type `Result<string, string>`
const answer = computationThatMightFail ( ) . match (
( str ) => str . toUpperCase ( ) ,
( err ) => `Error: ${ err } `
)
// `answer` is of type `string` Si no usa el parámetro de error en su devolución de llamada de coincidencia, entonces match es equivalente al map de encadenamiento con unwrapOr :
const answer = computationThatMightFail ( ) . match (
( str ) => str . toUpperCase ( ) ,
( ) => 'ComputationError'
)
// `answer` is of type `string`
const answer = computationThatMightFail ( )
. map ( ( str ) => str . toUpperCase ( ) )
. unwrapOr ( 'ComputationError' )⬆️ Volver a arriba
Result.asyncMap (método) Similar al map , excepto por dos cosas:
PromiseResultAsync Luego puede encadenar el resultado de asyncMap utilizando las API ResultAsync (como map , mapErr , andThen , etc.)
Firma:
class Result < T , E > {
asyncMap < U > (
callback : ( value : T ) => Promise < U >
) : ResultAsync < U , E > { ... }
}Ejemplo:
import { parseHeaders } from 'imaginary-http-parser'
// imagine that parseHeaders has the following signature:
// parseHeaders(raw: string): Result<SomeKeyValueMap, ParseError>
const asyncRes = parseHeaders ( rawHeader )
. map ( headerKvMap => headerKvMap . Authorization )
. asyncMap ( findUserInDatabase ) Tenga en cuenta que en el ejemplo anterior, si parseHeaders devuelven un Err , entonces .map y .asyncMap no se invocarán, y la variable asyncRes se resolverá a un Err cuando se convierte en un Result usando await o .then() .
⬆️ Volver a arriba
Result.andTee (método) Toma un Result<T, E> y permite que el Result<T, E> pase independientemente del resultado de la función aprobada. Esta es una forma útil de manejar los efectos secundarios cuyo fracaso o éxito no debe afectar sus lógicas principales, como el registro.
Firma:
class Result < T , E > {
andTee (
callback : ( value : T ) => unknown
) : Result < T , E > { ... }
}Ejemplo:
import { parseUserInput } from 'imaginary-parser'
import { logUser } from 'imaginary-logger'
import { insertUser } from 'imaginary-database'
// ^ assume parseUserInput, logUser and insertUser have the following signatures:
// parseUserInput(input: RequestData): Result<User, ParseError>
// logUser(user: User): Result<void, LogError>
// insertUser(user: User): ResultAsync<void, InsertError>
// Note logUser returns void upon success but insertUser takes User type.
const resAsync = parseUserInput ( userInput )
. andTee ( logUser )
. asyncAndThen ( insertUser )
// Note no LogError shows up in the Result type
resAsync . then ( ( res : Result < void , ParseError | InsertError > ) => { e
if ( res . isErr ( ) ) {
console . log ( "Oops, at least one step failed" , res . error )
}
else {
console . log ( "User input has been parsed and inserted successfully." )
}
} ) )⬆️ Volver a arriba
Result.andThrough (método) Similar a andTee excepto:
Firma:
class Result < T , E > {
andThrough < F > (
callback : ( value : T ) => Result < unknown , F >
) : Result < T , E | F > { ... }
}Ejemplo:
import { parseUserInput } from 'imaginary-parser'
import { validateUser } from 'imaginary-validator'
import { insertUser } from 'imaginary-database'
// ^ assume parseUseInput, validateUser and insertUser have the following signatures:
// parseUserInput(input: RequestData): Result<User, ParseError>
// validateUser(user: User): Result<void, ValidateError>
// insertUser(user: User): ResultAsync<void, InsertError>
// Note validateUser returns void upon success but insertUser takes User type.
const resAsync = parseUserInput ( userInput )
. andThrough ( validateUser )
. asyncAndThen ( insertUser )
resAsync . then ( ( res : Result < void , ParseErro | ValidateError | InsertError > ) => { e
if ( res . isErr ( ) ) {
console . log ( "Oops, at least one step failed" , res . error )
}
else {
console . log ( "User input has been parsed, validated, inserted successfully." )
}
} ) )⬆️ Volver a arriba
Result.asyncAndThrough (método) Similar a andThrough excepto que debe devolver un resultado.
Luego puede encadenar el resultado de asyncAndThrough utilizando las API ResultAsync (como map , mapErr , andThen , etc.)
Firma:
import { parseUserInput } from 'imaginary-parser'
import { insertUser } from 'imaginary-database'
import { sendNotification } from 'imaginary-service'
// ^ assume parseUserInput, insertUser and sendNotification have the following signatures:
// parseUserInput(input: RequestData): Result<User, ParseError>
// insertUser(user: User): ResultAsync<void, InsertError>
// sendNotification(user: User): ResultAsync<void, NotificationError>
// Note insertUser returns void upon success but sendNotification takes User type.
const resAsync = parseUserInput ( userInput )
. asyncAndThrough ( insertUser )
. andThen ( sendNotification )
resAsync . then ( ( res : Result < void , ParseError | InsertError | NotificationError > ) => { e
if ( res . isErr ( ) ) {
console . log ( "Oops, at least one step failed" , res . error )
}
else {
console . log ( "User has been parsed, inserted and notified successfully." )
}
} ) )⬆️ Volver a arriba
Result.fromThrowable (método de clase estática)Aunque el resultado no es una clase JS real, la forma en que se ha implementado
fromThrowablerequiere que llamefromThrowablecomo si fuera un método estático enResult. Ver ejemplos a continuación.
La comunidad de JavaScript ha acordado la convención de lanzar excepciones. Como tal, al interactuar con las bibliotecas de terceros, es imperativo que envuelva el código de terceros en bloques de prueba / captura.
Esta función creará una nueva función que devuelve un Err cuando se lanza la función original.
No es posible saber los tipos de los errores lanzados en la función original, por lo tanto, se recomienda usar el segundo errorFn de argumento para mapear lo que se arroja a un tipo conocido.
Ejemplo :
import { Result } from 'neverthrow'
type ParseError = { message : string }
const toParseError = ( ) : ParseError => ( { message : "Parse Error" } )
const safeJsonParse = Result . fromThrowable ( JSON . parse , toParseError )
// the function can now be used safely, if the function throws, the result will be an Err
const res = safeJsonParse ( "{" ) ;⬆️ Volver a arriba
Result.combine (método de clase estática)Aunque el resultado no es una clase JS real, la forma en que se ha implementado
combinerequiere que llamecombinecomo si fuera un método estático enResult. Ver ejemplos a continuación.
Combinar listas de Result s.
Si está familiarizado con Promise.all , la función Combinar funciona conceptualmente igual.
combine obras en listas heterogéneas y homogéneas . Esto significa que puede tener listas que contienen diferentes tipos de Result y aún así pueden combinarlas. Tenga en cuenta que no puede combinar listas que contengan los resultados Result y ResultAsync .
La función Combine toma una lista de resultados y devuelve un solo resultado. Si todos los resultados en la lista están Ok , el valor de retorno será un Ok contenido de una lista de todos los valores Ok individuales.
Si solo uno de los resultados en la lista es un Err , la función Combinar devuelve ese valor err (corta circuitos y devuelve el primer err que encuentra).
Formalmente hablando:
// homogeneous lists
function combine < T , E > ( resultList : Result < T , E > [ ] ) : Result < T [ ] , E >
// heterogeneous lists
function combine < T1 , T2 , E1 , E2 > ( resultList : [ Result < T1 , E1 > , Result < T2 , E2 > ] ) : Result < [ T1 , T2 ] , E1 | E2 >
function combine < T1 , T2 , T3 , E1 , E2 , E3 > => Result < [ T1 , T2 , T3 ] , E1 | E2 | E3 >
function combine < T1 , T2 , T3 , T4 , E1 , E2 , E3 , E4 > => Result < [ T1 , T2 , T3 , T4 ] , E1 | E2 | E3 | E4 >
// ... etc etc ad infinitumEjemplo:
const resultList : Result < number , never > [ ] =
[ ok ( 1 ) , ok ( 2 ) ]
const combinedList : Result < number [ ] , unknown > =
Result . combine ( resultList )Ejemplo con tuplas:
/** @example tuple(1, 2, 3) === [1, 2, 3] // with type [number, number, number] */
const tuple = < T extends any [ ] > ( ... args : T ) : T => args
const resultTuple : [ Result < string , never > , Result < string , never > ] =
tuple ( ok ( 'a' ) , ok ( 'b' ) )
const combinedTuple : Result < [ string , string ] , unknown > =
Result . combine ( resultTuple )⬆️ Volver a arriba
Result.combineWithAllErrors (método de clase estática)Aunque el resultado no es una clase JS real, la forma en que se ha implementado
combineWithAllErrorsrequiere que llamecombineWithAllErrorscomo si fuera un método estático enResult. Ver ejemplos a continuación.
Como combine pero sin cortocircuito. En lugar de solo el primer valor de error, obtiene una lista de todos los valores de error de la lista de resultados de entrada.
Si solo algunos resultados fallan, la nueva lista de errores combinados solo contendrá el valor de error de los resultados fallidos, lo que significa que no hay garantía de la longitud de la nueva lista de errores.
Firma de función:
// homogeneous lists
function combineWithAllErrors < T , E > ( resultList : Result < T , E > [ ] ) : Result < T [ ] , E [ ] >
// heterogeneous lists
function combineWithAllErrors < T1 , T2 , E1 , E2 > ( resultList : [ Result < T1 , E1 > , Result < T2 , E2 > ] ) : Result < [ T1 , T2 ] , ( E1 | E2 ) [ ] >
function combineWithAllErrors < T1 , T2 , T3 , E1 , E2 , E3 > => Result < [ T1 , T2 , T3 ] , ( E1 | E2 | E3 ) [ ] >
function combineWithAllErrors < T1 , T2 , T3 , T4 , E1 , E2 , E3 , E4 > => Result < [ T1 , T2 , T3 , T4 ] , ( E1 | E2 | E3 | E4 ) [ ] >
// ... etc etc ad infinitumEjemplo de uso:
const resultList : Result < number , string > [ ] = [
ok ( 123 ) ,
err ( 'boooom!' ) ,
ok ( 456 ) ,
err ( 'ahhhhh!' ) ,
]
const result = Result . combineWithAllErrors ( resultList )
// result is Err(['boooom!', 'ahhhhh!'])⬆️ Volver a arriba
Result.safeUnwrap()Desapercibido . Ya no necesitas usar este método.
Permite desenvolver un Result o devolver un Err implícitamente, reduciendo así a Boilerplate.
⬆️ Volver a arriba
ResultAsync ) okAsync Construye una variante Ok de ResultAsync
Firma:
okAsync < T , E > ( value : T ) : ResultAsync < T , E >Ejemplo:
import { okAsync } from 'neverthrow'
const myResultAsync = okAsync ( { myData : 'test' } ) // instance of `ResultAsync`
const myResult = await myResultAsync // instance of `Ok`
myResult . isOk ( ) // true
myResult . isErr ( ) // false⬆️ Volver a arriba
errAsync Construye una variante Err de ResultAsync
Firma:
errAsync < T , E > ( error : E ) : ResultAsync < T , E >Ejemplo:
import { errAsync } from 'neverthrow'
const myResultAsync = errAsync ( 'Oh nooo' ) // instance of `ResultAsync`
const myResult = await myResultAsync // instance of `Err`
myResult . isOk ( ) // false
myResult . isErr ( ) // true⬆️ Volver a arriba
ResultAsync.fromThrowable (método de clase estática) Similar al resultado. DeMhromThrowable, pero para funciones que devuelven una Promise .
Ejemplo :
import { ResultAsync } from 'neverthrow'
import { insertIntoDb } from 'imaginary-database'
// insertIntoDb(user: User): Promise<User>
const insertUser = ResultAsync . fromThrowable ( insertIntoDb , ( ) => new Error ( 'Database error' ) )
// `res` has a type of (user: User) => ResultAsync<User, Error> Tenga en cuenta que esto puede ser más seguro que el uso de resultados de resultados con el resultado de una llamada de función, porque no todas las funciones que devuelven una Promise son async y, por lo tanto, pueden lanzar errores sincrónicamente en lugar de devolver una Promise rechazada. Por ejemplo:
// NOT SAFE !!
import { ResultAsync } from 'neverthrow'
import { db } from 'imaginary-database'
// db.insert<T>(table: string, value: T): Promise<T>
const insertUser = ( user : User ) : Promise < User > => {
if ( ! user . id ) {
// this throws synchronously!
throw new TypeError ( 'missing user id' )
}
return db . insert ( 'users' , user )
}
// this will throw, NOT return a `ResultAsync`
const res = ResultAsync . fromPromise ( insertIntoDb ( myUser ) , ( ) => new Error ( 'Database error' ) )⬆️ Volver a arriba
ResultAsync.fromPromise (método de clase estática) Transforma un PromiseLike<T> (que puede arrojar) en un ResultAsync<T, E> .
El segundo argumento maneja el caso de rechazo de la promesa y asigna el error de unknown a algún tipo E .
Firma:
// fromPromise is a static class method
// also available as a standalone function
// import { fromPromise } from 'neverthrow'
ResultAsync . fromPromise < T , E > (
promise : PromiseLike < T > ,
errorHandler : ( unknownError : unknown ) => E )
) : ResultAsync < T , E > { ... } Si está trabajando con objetos PromiseLike que saben que no se lanzará, use fromSafePromise para evitar tener que aprobar un argumento redundante errorHandler .
Ejemplo :
import { ResultAsync } from 'neverthrow'
import { insertIntoDb } from 'imaginary-database'
// insertIntoDb(user: User): Promise<User>
const res = ResultAsync . fromPromise ( insertIntoDb ( myUser ) , ( ) => new Error ( 'Database error' ) )
// `res` has a type of ResultAsync<User, Error>⬆️ Volver a arriba
ResultAsync.fromSafePromise (método de clase estática) Igual que ResultAsync.fromPromise excepto que no maneja el rechazo de la promesa. Asegúrese de saber lo que está haciendo, de lo contrario, una excepción lanzada dentro de esta promesa hará que el resultado sea rechazado, en lugar de resolver un resultado.
Firma:
// fromPromise is a static class method
// also available as a standalone function
// import { fromPromise } from 'neverthrow'
ResultAsync . fromSafePromise < T , E > (
promise : PromiseLike < T >
) : ResultAsync < T , E > { ... }Ejemplo :
import { RouteError } from 'routes/error'
// simulate slow routes in an http server that works in a Result / ResultAsync context
// Adopted from https://github.com/parlez-vous/server/blob/2496bacf55a2acbebc30631b5562f34272794d76/src/routes/common/signup.ts
export const slowDown = < T > ( ms : number ) => ( value : T ) =>
ResultAsync . fromSafePromise < T , RouteError > (
new Promise ( ( resolve ) => {
setTimeout ( ( ) => {
resolve ( value )
} , ms )
} )
)
export const signupHandler = route < User > ( ( req , sessionManager ) =>
decode ( userSignupDecoder , req . body , 'Invalid request body' ) . map ( ( parsed ) => {
return createUser ( parsed )
. andThen ( slowDown ( 3000 ) ) // slowdown by 3 seconds
. andThen ( sessionManager . createSession )
. map ( ( { sessionToken , admin } ) => AppData . init ( admin , sessionToken ) )
} )
)⬆️ Volver a arriba
ResultAsync.map (método) Mapea un ResultAsync<T, E> a ResultAsync<U, E> aplicando una función a un valor Ok contenido, dejando un valor Err no
La función aplicada puede ser sincrónica o asincrónica (devolviendo una Promise<U> ) sin impacto en el tipo de retorno.
Esta función se puede utilizar para componer los resultados de dos funciones.
Firma:
class ResultAsync < T , E > {
map < U > (
callback : ( value : T ) => U | Promise < U >
) : ResultAsync < U , E > { ... }
}Ejemplo :
import { findUsersIn } from 'imaginary-database'
// ^ assume findUsersIn has the following signature:
// findUsersIn(country: string): ResultAsync<Array<User>, Error>
const usersInCanada = findUsersIn ( "Canada" )
// Let's assume we only need their names
const namesInCanada = usersInCanada . map ( ( users : Array < User > ) => users . map ( user => user . name ) )
// namesInCanada is of type ResultAsync<Array<string>, Error>
// We can extract the Result using .then() or await
namesInCanada . then ( ( namesResult : Result < Array < string > , Error > ) => {
if ( namesResult . isErr ( ) ) {
console . log ( "Couldn't get the users from the database" , namesResult . error )
}
else {
console . log ( "Users in Canada are named: " + namesResult . value . join ( ',' ) )
}
} )⬆️ Volver a arriba
ResultAsync.mapErr (método) Mapea un ResultAsync<T, E> a ResultAsync<T, F> aplicando una función a un valor Err contenido, dejando un valor de Ok intacto.
La función aplicada puede ser sincrónica o asincrónica (devolviendo una Promise<F> ) sin impacto en el tipo de retorno.
Esta función se puede usar para pasar por un resultado exitoso mientras maneja un error.
Firma:
class ResultAsync < T , E > {
mapErr < F > (
callback : ( error : E ) => F | Promise < F >
) : ResultAsync < T , F > { ... }
}Ejemplo :
import { findUsersIn } from 'imaginary-database'
// ^ assume findUsersIn has the following signature:
// findUsersIn(country: string): ResultAsync<Array<User>, Error>
// Let's say we need to low-level errors from findUsersIn to be more readable
const usersInCanada = findUsersIn ( "Canada" ) . mapErr ( ( error : Error ) => {
// The only error we want to pass to the user is "Unknown country"
if ( error . message === "Unknown country" ) {
return error . message
}
// All other errors will be labelled as a system error
return "System error, please contact an administrator."
} )
// usersInCanada is of type ResultAsync<Array<User>, string>
usersInCanada . then ( ( usersResult : Result < Array < User > , string > ) => {
if ( usersResult . isErr ( ) ) {
res . status ( 400 ) . json ( {
error : usersResult . error
} )
}
else {
res . status ( 200 ) . json ( {
users : usersResult . value
} )
}
} )⬆️ Volver a arriba
ResultAsync.unwrapOr (método) Desenviar el valor Ok o devuelva el valor predeterminado si hay un Err .
Funciona como Result.unwrapOr pero devuelve una Promise<T> en lugar de T .
Firma:
class ResultAsync < T , E > {
unwrapOr < T > ( value : T ) : Promise < T > { ... }
}Ejemplo :
const unwrapped : number = await errAsync ( 0 ) . unwrapOr ( 10 )
// unwrapped = 10⬆️ Volver a arriba
ResultAsync.andThen (método) Misma idea que map de arriba. Excepto que la función aplicada debe devolver un Result o ResultAsync .
ResultAsync.andThen siempre devuelve un ResultAsync sin importar el tipo de retorno de la función aplicada.
Esto es útil para cuando necesita hacer un cálculo posterior utilizando el valor T interno, pero ese cálculo puede fallar.
andThen es realmente útil como una herramienta para aplanar un ResultAsync<ResultAsync<A, E2>, E1> en un ResultAsync<A, E2> (ver ejemplo a continuación).
Firma:
// Note that the latest version (v4.1.0-beta) lets you return distinct errors as well.
// If the error types (E and F) are the same (like `string | string`)
// then they will be merged into one type (`string`)
class ResultAsync < T , E > {
andThen < U , F > (
callback : ( value : T ) => Result < U , F > | ResultAsync < U , F >
) : ResultAsync < U , E | F > { ... }
}Ejemplo
import { validateUser } from 'imaginary-validator'
import { insertUser } from 'imaginary-database'
import { sendNotification } from 'imaginary-service'
// ^ assume validateUser, insertUser and sendNotification have the following signatures:
// validateUser(user: User): Result<User, Error>
// insertUser(user): ResultAsync<User, Error>
// sendNotification(user): ResultAsync<void, Error>
const resAsync = validateUser ( user )
. andThen ( insertUser )
. andThen ( sendNotification )
// resAsync is a ResultAsync<void, Error>
resAsync . then ( ( res : Result < void , Error > ) => {
if ( res . isErr ( ) ) {
console . log ( "Oops, at least one step failed" , res . error )
}
else {
console . log ( "User has been validated, inserted and notified successfully." )
}
} )⬆️ Volver a arriba
ResultAsync.orElse (método) Toma un valor Err y lo asigna a un ResultAsync<T, SomeNewType> . Esto es útil para la recuperación de errores.
Firma:
class ResultAsync < T , E > {
orElse < U , A > (
callback : ( error : E ) => Result < U , A > | ResultAsync < U , A >
) : ResultAsync < U | T , A > { ... }
}⬆️ Volver a arriba
ResultAsync.match (método) Dadas 2 funciones (una para la variante Ok y otra para la variante Err ) ejecuta la función que coincide con la variante ResultAsync .
La diferencia con Result.match es que siempre devuelve una Promise debido a la naturaleza asíncrona de la ResultAsync .
Firma:
class ResultAsync < T , E > {
match < A , B = A > (
okCallback : ( value : T ) => A ,
errorCallback : ( error : E ) => B
) : Promise < A | B > => { ... }
}Ejemplo:
import { validateUser } from 'imaginary-validator'
import { insertUser } from 'imaginary-database'
// ^ assume validateUser and insertUser have the following signatures:
// validateUser(user: User): Result<User, Error>
// insertUser(user): ResultAsync<User, Error>
// Handle both cases at the end of the chain using match
const resultMessage = await validateUser ( user )
. andThen ( insertUser )
. match (
( user : User ) => `User ${ user . name } has been successfully created` ,
( error : Error ) => `User could not be created because ${ error . message } `
)
// resultMessage is a string⬆️ Volver a arriba
ResultAsync.andTee (método) Toma un ResultAsync<T, E> y permite que el ResultAsync<T, E> pase independientemente del resultado de la función aprobada. Esta es una forma útil de manejar los efectos secundarios cuyo fracaso o éxito no debe afectar sus lógicas principales, como el registro.
Firma:
class ResultAsync < T , E > {
andTee (
callback : ( value : T ) => unknown
) : ResultAsync < T , E > => { ... }
}Ejemplo:
import { insertUser } from 'imaginary-database'
import { logUser } from 'imaginary-logger'
import { sendNotification } from 'imaginary-service'
// ^ assume insertUser, logUser and sendNotification have the following signatures:
// insertUser(user: User): ResultAsync<User, InsertError>
// logUser(user: User): Result<void, LogError>
// sendNotification(user: User): ResultAsync<void, NotificationError>
// Note logUser returns void on success but sendNotification takes User type.
const resAsync = insertUser ( user )
. andTee ( logUser )
. andThen ( sendNotification )
// Note there is no LogError in the types below
resAsync . then ( ( res : Result < void , InsertError | NotificationError > ) => { e
if ( res . isErr ( ) ) {
console . log ( "Oops, at least one step failed" , res . error )
}
else {
console . log ( "User has been inserted and notified successfully." )
}
} ) ) ⬆️ Volver a arriba
ResultAsync.andThrough (método) Similar a andTee excepto:
Firma:
class ResultAsync < T , E > {
andThrough < F > (
callback : ( value : T ) => Result < unknown , F > | ResultAsync < unknown , F > ,
) : ResultAsync < T , E | F > => { ... }
}Ejemplo:
import { buildUser } from 'imaginary-builder'
import { insertUser } from 'imaginary-database'
import { sendNotification } from 'imaginary-service'
// ^ assume buildUser, insertUser and sendNotification have the following signatures:
// buildUser(userRaw: UserRaw): ResultAsync<User, BuildError>
// insertUser(user: User): ResultAsync<void, InsertError>
// sendNotification(user: User): ResultAsync<void, NotificationError>
// Note insertUser returns void upon success but sendNotification takes User type.
const resAsync = buildUser ( userRaw )
. andThrough ( insertUser )
. andThen ( sendNotification )
resAsync . then ( ( res : Result < void , BuildError | InsertError | NotificationError > ) => { e
if ( res . isErr ( ) ) {
console . log ( "Oops, at least one step failed" , res . error )
}
else {
console . log ( "User data has been built, inserted and notified successfully." )
}
} ) ) ⬆️ Volver a arriba
ResultAsync.combine (método de clase estática) Combinar listas de ResultAsync .
Si está familiarizado con Promise.all , la función Combinar funciona conceptualmente igual.
combine obras en listas heterogéneas y homogéneas . Esto significa que puede tener listas que contienen diferentes tipos de ResultAsync y aún así pueden combinarlas. Tenga en cuenta que no puede combinar listas que contengan los resultados Result y ResultAsync .
La función Combine toma una lista de resultados y devuelve un solo resultado. Si todos los resultados en la lista están Ok , el valor de retorno será un Ok contenido de una lista de todos los valores Ok individuales.
Si solo uno de los resultados en la lista es un Err , la función Combinar devuelve ese valor err (corta circuitos y devuelve el primer err que encuentra).
Formalmente hablando:
// homogeneous lists
function combine < T , E > ( resultList : ResultAsync < T , E > [ ] ) : ResultAsync < T [ ] , E >
// heterogeneous lists
function combine < T1 , T2 , E1 , E2 > ( resultList : [ ResultAsync < T1 , E1 > , ResultAsync < T2 , E2 > ] ) : ResultAsync < [ T1 , T2 ] , E1 | E2 >
function combine < T1 , T2 , T3 , E1 , E2 , E3 > => ResultAsync < [ T1 , T2 , T3 ] , E1 | E2 | E3 >
function combine < T1 , T2 , T3 , T4 , E1 , E2 , E3 , E4 > => ResultAsync < [ T1 , T2 , T3 , T4 ] , E1 | E2 | E3 | E4 >
// ... etc etc ad infinitumEjemplo:
const resultList : ResultAsync < number , never > [ ] =
[ okAsync ( 1 ) , okAsync ( 2 ) ]
const combinedList : ResultAsync < number [ ] , unknown > =
ResultAsync . combine ( resultList )Ejemplo con tuplas:
/** @example tuple(1, 2, 3) === [1, 2, 3] // with type [number, number, number] */
const tuple = < T extends any [ ] > ( ... args : T ) : T => args
const resultTuple : [ ResultAsync < string , never > , ResultAsync < string , never > ] =
tuple ( okAsync ( 'a' ) , okAsync ( 'b' ) )
const combinedTuple : ResultAsync < [ string , string ] , unknown > =
ResultAsync . combine ( resultTuple )⬆️ Volver a arriba
ResultAsync.combineWithAllErrors (método de clase estática) Como combine pero sin cortocircuito. En lugar de solo el primer valor de error, obtiene una lista de todos los valores de error de la lista de resultados de entrada.
Si solo algunos resultados fallan, la nueva lista de errores combinados solo contendrá el valor de error de los resultados fallidos, lo que significa que no hay garantía de la longitud de la nueva lista de errores.
Firma de función:
// homogeneous lists
function combineWithAllErrors < T , E > ( resultList : ResultAsync < T , E > [ ] ) : ResultAsync < T [ ] , E [ ] >
// heterogeneous lists
function combineWithAllErrors < T1 , T2 , E1 , E2 > ( resultList : [ ResultAsync < T1 , E1 > , ResultAsync < T2 , E2 > ] ) : ResultAsync < [ T1 , T2 ] , ( E1 | E2 ) [ ] >
function combineWithAllErrors < T1 , T2 , T3 , E1 , E2 , E3 > => ResultAsync < [ T1 , T2 , T3 ] , ( E1 | E2 | E3 ) [ ] >
function combineWithAllErrors < T1 , T2 , T3 , T4 , E1 , E2 , E3 , E4 > => ResultAsync < [ T1 , T2 , T3 , T4 ] , ( E1 | E2 | E3 | E4 ) [ ] >
// ... etc etc ad infinitumEjemplo de uso:
const resultList : ResultAsync < number , string > [ ] = [
okAsync ( 123 ) ,
errAsync ( 'boooom!' ) ,
okAsync ( 456 ) ,
errAsync ( 'ahhhhh!' ) ,
]
const result = ResultAsync . combineWithAllErrors ( resultList )
// result is Err(['boooom!', 'ahhhhh!']) ResultAsync.safeUnwrap()Desapercibido . Ya no necesitas usar este método.
Permite desenvolver un Result o devolver un Err implícitamente, reduciendo así a Boilerplate.
⬆️ Volver a arriba
fromThrowable Exportación de Result.fromThrowable de nivel superior. Encuentre la documentación al resultado.
⬆️ Volver a arriba
fromAsyncThrowable Exportación de nivel superior de ResultAsync.fromThrowable . Encuentre documentación en ResultAsync.FromThrowable
⬆️ Volver a arriba
fromPromise Exportación de nivel superior de ResultAsync.fromPromise . Encuentre documentación en el resultado Tautasync.Frompromise
⬆️ Volver a arriba
fromSafePromise Exportación de nivel superior de ResultAsync.fromSafePromise . Encuentre documentación en ResultAsync.FromSafePromise
⬆️ Volver a arriba
safeTrySe utiliza para devolver implícitamente los errores y reducir la caldera.
Digamos que estamos escribiendo una función que devuelve un Result , y en esa función llamamos algunas funciones que también devuelven Result y verificamos esos resultados para ver si debemos seguir o abortar. Por lo general, escribiremos como lo siguiente.
declare function mayFail1 ( ) : Result < number , string > ;
declare function mayFail2 ( ) : Result < number , string > ;
function myFunc ( ) : Result < number , string > {
// We have to define a constant to hold the result to check and unwrap its value.
const result1 = mayFail1 ( ) ;
if ( result1 . isErr ( ) ) {
return err ( `aborted by an error from 1st function, ${ result1 . error } ` ) ;
}
const value1 = result1 . value
// Again, we need to define a constant and then check and unwrap.
const result2 = mayFail2 ( ) ;
if ( result2 . isErr ( ) ) {
return err ( `aborted by an error from 2nd function, ${ result2 . error } ` ) ;
}
const value2 = result2 . value
// And finally we return what we want to calculate
return ok ( value1 + value2 ) ;
} Básicamente, debemos definir una constante para cada resultado para verificar si está Ok y leer su .value o .error .
Con Safetry, podemos indicar 'regresar aquí si es un Err , de lo contrario desenvolverlo aquí y continuar'. en una sola expresión.
declare function mayFail1 ( ) : Result < number , string > ;
declare function mayFail2 ( ) : Result < number , string > ;
function myFunc ( ) : Result < number , string > {
return safeTry < number , string > ( function * ( ) {
return ok (
// If the result of mayFail1().mapErr() is an `Err`, the evaluation is
// aborted here and the enclosing `safeTry` block is evaluated to that `Err`.
// Otherwise, this `(yield* ...)` is evaluated to its `.value`.
( yield * mayFail1 ( )
. mapErr ( e => `aborted by an error from 1st function, ${ e } ` ) )
+
// The same as above.
( yield * mayFail2 ( )
. mapErr ( e => `aborted by an error from 2nd function, ${ e } ` ) )
)
} )
} Para usar safeTry , los puntos son los siguientes.
yield* <RESULT> para declarar 'return <RESULT> si es un Err , de lo contrario evaluar su .value 'safeTry También puede usar la función del generador Async para pasar un bloque de async a safeTry .
// You can use either Promise<Result> or ResultAsync.
declare function mayFail1 ( ) : Promise < Result < number , string > > ;
declare function mayFail2 ( ) : ResultAsync < number , string > ;
function myFunc ( ) : Promise < Result < number , string > > {
return safeTry < number , string > ( async function * ( ) {
return ok (
// You have to await if the expression is Promise<Result>
( yield * ( await mayFail1 ( ) )
. mapErr ( e => `aborted by an error from 1st function, ${ e } ` ) )
+
// You can call `safeUnwrap` directly if its ResultAsync
( yield * mayFail2 ( )
. mapErr ( e => `aborted by an error from 2nd function, ${ e } ` ) )
)
} )
}Para obtener más información, consulte #448 y #444
⬆️ Volver a arriba
Las instancias Result tienen dos métodos inseguros, acertadamente llamados _unsafeUnwrap y _unsafeUnwrapErr que solo deben usarse en un entorno de prueba .
_unsafeUnwrap toma un Result<T, E> y devuelve una T cuando el resultado es un Ok
_unsafeUnwrapErr toma un Result<T, E> y devuelve una E cuando el resultado es un Err , de lo contrario lanza un objeto personalizado.
De esa manera puedes hacer algo como:
expect ( myResult . _unsafeUnwrap ( ) ) . toBe ( someExpectation ) Sin embargo, tenga en cuenta que las instancias Result son comparables. Por lo tanto, no necesariamente necesita desenvolverlos para afirmar las expectativas en sus pruebas. Entonces también podrías hacer algo como esto:
import { ok } from 'neverthrow'
// ...
expect ( callSomeFunctionThatReturnsAResult ( "with" , "some" , "args" ) ) . toEqual ( ok ( someExpectation ) ) ; Por defecto, el valor lanzado no contiene un rastro de pila. Esto se debe a que la generación de rastreo de pila hace que los mensajes de error en la broma sean más difícil de entender. Si desea que se generen trazas de pila, llame _unsafeUnwrap y / o _unsafeUnwrapErr con un objeto de configuración:
_unsafeUnwrapErr ( {
withStackTrace : true ,
} )
// ^ Now the error object will have a `.stack` property containing the current stackSi encuentra útil este paquete, considere patrocinarme o simplemente comprarme un café.
Aunque el paquete se llama neverthrow , no tome esto literalmente. Simplemente estoy alentando al desarrollador a pensar un poco más sobre la ergonomía y el uso de cualquier software que estén escribiendo.
Throw y catching es muy similar al uso de declaraciones goto , en otras palabras; Hace que el razonamiento sobre sus programas sea más difícil. En segundo lugar, al usar throw , supone que la persona que llama está implementando catch . Esta es una fuente conocida de errores. Ejemplo: un throw de desarrollo y otro Dev usa la función sin conocimiento previo que lanzará la función. Por lo tanto, y el caso de borde se ha dejado sin control y ahora tiene usuarios infelices, jefes, gatos, etc.
Con todo lo dicho, definitivamente hay buenos casos de uso para lanzar su programa. Pero mucho menos de lo que piensas.
El proyecto NeverThrow está disponible como código abierto bajo los términos de la licencia MIT.