
Codificar a falha no seu programa.
Este pacote contém um tipo Result que representa sucesso ( Ok ) ou falha ( Err ).
Para tarefas assíncronas, neverthrow oferece uma classe ResultAsync , que envolve uma Promise<Result<T, E>> e fornece o mesmo nível de expressividade e controle que um Result<T, E> .
ResultAsync é thenable , o que significa que se comporta exatamente como uma Promise<Result> ... exceto que você tem acesso aos mesmos métodos que Result fornece sem precisar await ou .then a promessa! Confira o wiki para obter exemplos e práticas recomendadas.
Precisa ver exemplos da vida real de como aproveitar este pacote para tratamento de erros? Veja este repo: 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 (método)Result.asyncAndThen (Método)Result.orElse (método)Result.match (Método)Result.asyncMap (método)Result.andTee (método)Result.andThrough .Result.asyncAndThrough (método)Result.fromThrowable (método de classe estática)Result.combine (método de classe estática)Result.combineWithAllErrors (método de classe estática)Result.safeUnwrap()ResultAsync )okAsyncerrAsyncResultAsync.fromThrowable (método de classe estática)ResultAsync.fromPromise (método de classe estática)ResultAsync.fromSafePromise (método de classe 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 classe estática)ResultAsync.combineWithAllErrors (método de classe estática)ResultAsync.safeUnwrap()fromThrowablefromAsyncThrowablefromPromisefromSafePromisesafeTry > npm install neverthroweslint-plugin-neverthrow Como parte do programa de recompensa do neverthrow , o usuário Mdberancourt criou eslint-plugin-neverthrow para garantir que os erros não tenham saído.
Instale em execução:
> npm install eslint-plugin-neverthrow Com eslint-plugin-neverthrow , você é forçado a consumir o resultado em uma das três maneiras a seguir:
.match.unwrapOr._unsafeUnwrap Isso garante que você esteja lidando explicitamente com o erro do seu Result .
Este plug-in é essencialmente uma porção do atributo must-use de Rust.
neverthrow expõe o seguinte:
ok função de conveniência para criar uma variante Ok do ResultErr err ResultOk aula e tipoErr CLASSE E TIPOResult , bem como namespace / objeto a partir do qual chama Result.fromThrowable .ResultAsync ClasseokAsync para criar um ResultAsync que contenha um Result Ok TypeerrAsync para criar um ResultAsync que contenha um Result de Err import {
ok ,
Ok ,
err ,
Err ,
Result ,
okAsync ,
errAsync ,
ResultAsync ,
fromAsyncThrowable ,
fromThrowable ,
fromPromise ,
fromSafePromise ,
safeTry ,
} from 'neverthrow' Confira o Wiki para obter ajuda sobre como aproveitar ao máximo a neverthrow .
Se você achar esse pacote útil, considere me patrocinar ou simplesmente me comprar um café!
Result ) ok Construa uma variante Ok de Result
Assinatura:
ok < T , E > ( value : T ) : Ok < T , E > { ... }Exemplo:
import { ok } from 'neverthrow'
const myResult = ok ( { myData : 'test' } ) // instance of `Ok`
myResult . isOk ( ) // true
myResult . isErr ( ) // false⬆️ de volta ao topo
err Construa uma variante de Err do Result
Assinatura:
err < T , E > ( error : E ) : Err < T , E > { ... }Exemplo:
import { err } from 'neverthrow'
const myResult = err ( 'Oh noooo' ) // instance of `Err`
myResult . isOk ( ) // false
myResult . isErr ( ) // true⬆️ de volta ao topo
Result.isOk (Método) Retorna true se o resultado for uma variante Ok
Assinatura:
isOk ( ) : boolean { ... }⬆️ de volta ao topo
Result.isErr (método) Retorna true se o resultado for uma variante Err
Assinatura :
isErr ( ) : boolean { ... }⬆️ de volta ao topo
Result.map (método) Mapas Um Result<T, E> para Result<U, E> aplicando uma função a um valor Ok contido, deixando um valor Err intocado.
Esta função pode ser usada para compor os resultados de duas funções.
Assinatura:
class Result < T , E > {
map < U > ( callback : ( value : T ) => U ) : Result < U , E > { ... }
}Exemplo :
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⬆️ de volta ao topo
Result.mapErr (Método) Mapas Um Result<T, E> para Result<T, F> aplicando uma função a um valor Err contido, deixando um valor Ok intocado.
Essa função pode ser usada para passar por um resultado bem -sucedido ao lidar com um erro.
Assinatura:
class Result < T , E > {
mapErr < F > ( callback : ( error : E ) => F ) : Result < T , F > { ... }
}Exemplo :
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⬆️ de volta ao topo
Result.unwrapOr (Método) Desembrulhar o valor Ok ou retornar o padrão se houver um Err
Assinatura:
class Result < T , E > {
unwrapOr < T > ( value : T ) : T { ... }
}Exemplo :
const myResult = err ( 'Oh noooo' )
const multiply = ( value : number ) : number => value * 2
const unwrapped : number = myResult . map ( multiply ) . unwrapOr ( 10 )⬆️ de volta ao topo
Result.andThen (método) A mesma ideia que map acima. Exceto que você deve devolver um novo Result .
O valor retornado será Result . A partir da v4.1.0-beta , você pode retornar tipos de erro distintos (consulte a assinatura abaixo). Antes da v4.1.0-beta , o tipo de erro não poderia ser distinto.
Isso é útil para quando você precisa fazer uma computação subsequente usando o valor T interno, mas esse cálculo pode falhar.
Além disso, andThen é realmente útil como uma ferramenta para achatar um Result<Result<A, E2>, E1> em um Result<A, E2> (veja o exemplo abaixo).
Assinatura:
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 > { ... }
}Exemplo 1: Resultados de encadeamento
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)Exemplo 2: Resultados aninhados achatados
// It's common to have nested Results
const nested = ok ( ok ( 1234 ) )
// notNested is a Ok(1234)
const notNested = nested . andThen ( ( innerResult ) => innerResult )⬆️ de volta ao topo
Result.asyncAndThen (Método) A mesma idéia que andThen por isso, exceto que você deve devolver um novo ResultAsync .
O valor retornado será um ResultAsync .
Assinatura:
class Result < T , E > {
asyncAndThen < U , F > (
callback : ( value : T ) => ResultAsync < U , F >
) : ResultAsync < U , E | F > { ... }
}⬆️ de volta ao topo
Result.orElse (método) Pega um valor Err e mapeia -o para um Result<T, SomeNewType> . Isso é útil para recuperação de erros.
Assinatura:
class Result < T , E > {
orElse < U , A > (
callback : ( error : E ) => Result < U , A >
) : Result < U | T , A > { ... }
}Exemplo:
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 )
)⬆️ de volta ao topo
Result.match (Método) Dadas 2 funções (uma para a variante Ok e outra para a variante Err ) executam a função que corresponde à variante Result .
Os retornos de chamada correspondentes não precisam de retornar um Result , mas você pode retornar um Result , se desejar.
Assinatura:
class Result < T , E > {
match < A , B = A > (
okCallback : ( value : T ) => A ,
errorCallback : ( error : E ) => B
) : A | B => { ... }
} match é como encadear map e mapErr , com a distinção que, match as duas funções, deve ter o mesmo tipo de retorno. As diferenças entre match e map de encadeamento e mapErr são:
match ambas as funções devem ter o mesmo tipo de retorno Amatch Unwrraps o Result<T, E> em um A (o tipo de retorno das funções da partida)Exemplo:
// 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` Se você não usar o parâmetro de erro em seu retorno de chamada de correspondência, match é equivalente a encadear map com unwrapOr :
const answer = computationThatMightFail ( ) . match (
( str ) => str . toUpperCase ( ) ,
( ) => 'ComputationError'
)
// `answer` is of type `string`
const answer = computationThatMightFail ( )
. map ( ( str ) => str . toUpperCase ( ) )
. unwrapOr ( 'ComputationError' )⬆️ de volta ao topo
Result.asyncMap (método) Semelhante ao map , exceto por duas coisas:
PromiseResultAsync Em seguida, você pode acorrentar o resultado do asyncMap usando as APIs ResultAsync (como map , mapErr andThen , etc.)
Assinatura:
class Result < T , E > {
asyncMap < U > (
callback : ( value : T ) => Promise < U >
) : ResultAsync < U , E > { ... }
}Exemplo:
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 ) Observe que, no exemplo acima, se parseHeaders retornar um Err , então .map e .asyncMap não serão invocados e a variável asyncRes será resolvida para um Err quando transformado em um Result await ou .then()
⬆️ de volta ao topo
Result.andTee (método) Requer um Result<T, E> e permite que o Result<T, E> passe independentemente do resultado da função aprovada. Esta é uma maneira útil de lidar com efeitos colaterais cuja falha ou sucesso não deve afetar suas lógicas principais, como o log.
Assinatura:
class Result < T , E > {
andTee (
callback : ( value : T ) => unknown
) : Result < T , E > { ... }
}Exemplo:
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." )
}
} ) )⬆️ de volta ao topo
Result.andThrough . Semelhante a andTee exceto:
Assinatura:
class Result < T , E > {
andThrough < F > (
callback : ( value : T ) => Result < unknown , F >
) : Result < T , E | F > { ... }
}Exemplo:
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." )
}
} ) )⬆️ de volta ao topo
Result.asyncAndThrough (método) Semelhante andThrough exceto que você deve devolver um resultado.
Em seguida, você pode acorrentar o resultado de asyncAndThrough usando as APIs ResultAsync (como map , mapErr andThen etc.)
Assinatura:
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." )
}
} ) )⬆️ de volta ao topo
Result.fromThrowable (método de classe estática)Embora o resultado não seja uma classe JS real, a maneira como a
fromThrowablefoi implementada exige que você chamafromThrowablecomo se fosse um método estático noResult. Veja os exemplos abaixo.
A comunidade JavaScript concordou com a Convenção de Exceções. Como tal, ao interface com as bibliotecas de terceiros, é imperativo que você envolva o código de terceiros nos blocos de tentativa / captura.
Esta função criará uma nova função que retornará um Err quando a função original for lançada.
Não é possível saber os tipos de erros lançados na função original; portanto, é recomendável usar o segundo argumento errorFn para mapear o que é jogado para um tipo conhecido.
Exemplo :
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 ( "{" ) ;⬆️ de volta ao topo
Result.combine (método de classe estática)Embora o resultado não seja uma classe JS real, a maneira como a
combinefoi implementada exige que você chamacombinecomo se fosse um método estático noResult. Veja os exemplos abaixo.
Combine listas de Result s.
Se você está familiarizado com Promise.all Tudo, a função Combine funciona conceitualmente da mesma forma.
combine trabalhos em listas heterogêneas e homogêneas . Isso significa que você pode ter listas que contêm diferentes tipos de Result e ainda podem combiná -las. Observe que você não pode combinar listas que contenham Result e ResultAsync .
A função Combine pega uma lista de resultados e retorna um único resultado. Se todos os resultados da lista estiverem Ok , o valor de retorno será um Ok contendo uma lista de todos os valores individuais Ok .
Se apenas um dos resultados da lista for um Err , a função Combine retorna esse valor de erro (ele curta circuitos e retorna o primeiro erro que encontra).
Formalmente falando:
// 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 infinitumExemplo:
const resultList : Result < number , never > [ ] =
[ ok ( 1 ) , ok ( 2 ) ]
const combinedList : Result < number [ ] , unknown > =
Result . combine ( resultList )Exemplo com 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 )⬆️ de volta ao topo
Result.combineWithAllErrors (método de classe estática)Embora o resultado não seja uma classe JS real, a maneira como
combineWithAllErrorsfoi implementada exige que você ligue paracombineWithAllErrorscomo se fosse um método estático noResult. Veja os exemplos abaixo.
Como combine , mas sem curto-circuito. Em vez do primeiro valor de erro, você obtém uma lista de todos os valores de erro da lista de resultados de entrada.
Se apenas alguns resultados falharem, a nova lista de erros combinados conterá apenas o valor de erro dos resultados com falha, o que significa que não há garantia do comprimento da nova lista de erros.
Assinatura da função:
// 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 infinitumExemplo 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!'])⬆️ de volta ao topo
Result.safeUnwrap()Descontinuado . Você não precisa mais usar esse método.
Permite desembrulhar um Result ou retornar um Err implicitamente, reduzindo assim a caldeira.
⬆️ de volta ao topo
ResultAsync ) okAsync Construa uma variante Ok do ResultAsync
Assinatura:
okAsync < T , E > ( value : T ) : ResultAsync < T , E >Exemplo:
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⬆️ de volta ao topo
errAsync Construa uma variante Err do ResultAsync
Assinatura:
errAsync < T , E > ( error : E ) : ResultAsync < T , E >Exemplo:
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⬆️ de volta ao topo
ResultAsync.fromThrowable (método de classe estática) Semelhante ao resultado Promise
Exemplo :
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> Observe que isso pode ser mais seguro do que usar o resultAsync.FROMISTE com o resultado de uma chamada de função, porque nem todas as funções que retornam uma Promise são async e, portanto, elas podem lançar erros de maneira síncrona, em vez de retornar uma Promise rejeitada. Por exemplo:
// 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' ) )⬆️ de volta ao topo
ResultAsync.fromPromise (método de classe estática) Transforma um PromiseLike<T> (que pode lançar) em um ResultAsync<T, E> .
O segundo argumento lida com o caso de rejeição da promessa e mapeia o erro do unknown para algum tipo E .
Assinatura:
// 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 > { ... } Se você estiver trabalhando com objetos PromiseLike que você conhece de fato , não será lançado, use fromSafePromise para evitar ter que passar por um argumento redundante errorHandler .
Exemplo :
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>⬆️ de volta ao topo
ResultAsync.fromSafePromise (método de classe estática) O mesmo que ResultAsync.fromPromise exceto que não lida com a rejeição da promessa. Certifique -se de saber o que está fazendo, caso contrário, uma exceção lançada nessa promessa fará com que o ResultAsync rejeite, em vez de resolver um resultado.
Assinatura:
// 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 > { ... }Exemplo :
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 ) )
} )
)⬆️ de volta ao topo
ResultAsync.map (método) Mapas Um ResultAsync<T, E> para ResultAsync<U, E> aplicando uma função a um valor Ok contido, deixando um valor de Err intocado.
A função aplicada pode ser síncrona ou assíncrona (retornando uma Promise<U> ) sem impacto no tipo de retorno.
Esta função pode ser usada para compor os resultados de duas funções.
Assinatura:
class ResultAsync < T , E > {
map < U > (
callback : ( value : T ) => U | Promise < U >
) : ResultAsync < U , E > { ... }
}Exemplo :
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 ( ',' ) )
}
} )⬆️ de volta ao topo
ResultAsync.mapErr (Método) Mapas Um ResultAsync<T, E> para ResultAsync<T, F> aplicando uma função a um valor Err contido, deixando um valor Ok intocado.
A função aplicada pode ser síncrona ou assíncrona (retornando uma Promise<F> ) sem impacto no tipo de retorno.
Essa função pode ser usada para passar por um resultado bem -sucedido ao lidar com um erro.
Assinatura:
class ResultAsync < T , E > {
mapErr < F > (
callback : ( error : E ) => F | Promise < F >
) : ResultAsync < T , F > { ... }
}Exemplo :
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
} )
}
} )⬆️ de volta ao topo
ResultAsync.unwrapOr (método) Desembrulhar o valor Ok ou retornar o padrão se houver um Err .
Funciona como Result.unwrapOr , mas retorna uma Promise<T> em vez de T .
Assinatura:
class ResultAsync < T , E > {
unwrapOr < T > ( value : T ) : Promise < T > { ... }
}Exemplo :
const unwrapped : number = await errAsync ( 0 ) . unwrapOr ( 10 )
// unwrapped = 10⬆️ de volta ao topo
ResultAsync.andThen (Método) A mesma ideia que map acima. Exceto que a função aplicada deve retornar um Result ou ResultAsync .
ResultAsync.andThen sempre retorna um ResultAsync independentemente do tipo de retorno da função aplicada.
Isso é útil para quando você precisa fazer uma computação subsequente usando o valor T interno, mas esse cálculo pode falhar.
andThen é realmente útil como uma ferramenta para achatar um ResultAsync<ResultAsync<A, E2>, E1> em um ResultAsync<A, E2> (veja o exemplo abaixo).
Assinatura:
// 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 > { ... }
}Exemplo
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." )
}
} )⬆️ de volta ao topo
ResultAsync.orElse (Método) Pega um valor Err e mapeia -o para um ResultAsync<T, SomeNewType> . Isso é útil para recuperação de erros.
Assinatura:
class ResultAsync < T , E > {
orElse < U , A > (
callback : ( error : E ) => Result < U , A > | ResultAsync < U , A >
) : ResultAsync < U | T , A > { ... }
}⬆️ de volta ao topo
ResultAsync.match (método) Dadas 2 funções (uma para a variante Ok e outra para a variante Err ) executam a função que corresponde à variante ResultAsync .
A diferença com Result.match é que ele sempre retorna uma Promise devido à natureza assíncrona do ResultAsync .
Assinatura:
class ResultAsync < T , E > {
match < A , B = A > (
okCallback : ( value : T ) => A ,
errorCallback : ( error : E ) => B
) : Promise < A | B > => { ... }
}Exemplo:
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⬆️ de volta ao topo
ResultAsync.andTee (Método) Pega um ResultAsync<T, E> , e permite que o ResultAsync<T, E> original tenha passado, independentemente do resultado da função aprovada. Esta é uma maneira útil de lidar com efeitos colaterais cuja falha ou sucesso não deve afetar suas lógicas principais, como o log.
Assinatura:
class ResultAsync < T , E > {
andTee (
callback : ( value : T ) => unknown
) : ResultAsync < T , E > => { ... }
}Exemplo:
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." )
}
} ) ) ⬆️ de volta ao topo
ResultAsync.andThrough (método) Semelhante a andTee exceto:
Assinatura:
class ResultAsync < T , E > {
andThrough < F > (
callback : ( value : T ) => Result < unknown , F > | ResultAsync < unknown , F > ,
) : ResultAsync < T , E | F > => { ... }
}Exemplo:
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." )
}
} ) ) ⬆️ de volta ao topo
ResultAsync.combine (método de classe estática) Combine listas de ResultAsync s.
Se você está familiarizado com Promise.all Tudo, a função Combine funciona conceitualmente da mesma forma.
combine trabalhos em listas heterogêneas e homogêneas . Isso significa que você pode ter listas que contêm diferentes tipos de ResultAsync e ainda podem combiná -las. Observe que você não pode combinar listas que contenham Result e ResultAsync .
A função Combine pega uma lista de resultados e retorna um único resultado. Se todos os resultados da lista estiverem Ok , o valor de retorno será um Ok contendo uma lista de todos os valores individuais Ok .
Se apenas um dos resultados da lista for um Err , a função Combine retorna esse valor de erro (ele curta circuitos e retorna o primeiro erro que encontra).
Formalmente falando:
// 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 infinitumExemplo:
const resultList : ResultAsync < number , never > [ ] =
[ okAsync ( 1 ) , okAsync ( 2 ) ]
const combinedList : ResultAsync < number [ ] , unknown > =
ResultAsync . combine ( resultList )Exemplo com 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 )⬆️ de volta ao topo
ResultAsync.combineWithAllErrors (método de classe estática) Como combine , mas sem curto-circuito. Em vez do primeiro valor de erro, você obtém uma lista de todos os valores de erro da lista de resultados de entrada.
Se apenas alguns resultados falharem, a nova lista de erros combinados conterá apenas o valor de erro dos resultados com falha, o que significa que não há garantia do comprimento da nova lista de erros.
Assinatura da função:
// 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 infinitumExemplo 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()Descontinuado . Você não precisa mais usar esse método.
Permite desembrulhar um Result ou retornar um Err implicitamente, reduzindo assim a caldeira.
⬆️ de volta ao topo
fromThrowable Exportação de nível superior do Result.fromThrowable . Por favor, encontre documentação no resultado.FroceLableable
⬆️ de volta ao topo
fromAsyncThrowable Exportação de nível superior de ResultAsync.fromThrowable . Por favor, encontre documentação em resulteSync.Frowrowable
⬆️ de volta ao topo
fromPromise Exportação de nível superior de ResultAsync.fromPromise . Encontre a documentação no ResultAsync.FromProme
⬆️ de volta ao topo
fromSafePromise Exportação de nível superior de ResultAsync.fromSafePromise . Encontre a documentação no Resultasync.fromsfepromise
⬆️ de volta ao topo
safeTryUsado para retornar implicitamente erros e reduzir a caldeira.
Digamos que estamos escrevendo uma função que retorne um Result e, nessa função, chamamos algumas funções que também retornam Result e verificamos esses resultados para verificar se devemos continuar ou abortar. Geralmente, escreveremos como o seguinte.
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 ) ;
} Basicamente, precisamos definir uma constante para cada resultado para verificar se é um Ok e ler seu .value ou .error .
Com a segurança, podemos declarar 'retornar aqui se for um Err , caso contrário, desembrulhá -lo aqui e continuar'. em apenas uma expressão.
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 , os pontos são os seguintes.
yield* <RESULT> para declarar 'Retorno <RESULT> Se for um Err , caso contrário, avalie seu .value 'safeTry Você também pode usar a função do gerador assíncrono para passar um bloco assíncrono em 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 mais informações, consulte #448 e #444
⬆️ de volta ao topo
As instâncias Result têm dois métodos inseguros, apropriadamente chamados de _unsafeUnwrap e _unsafeUnwrapErr que devem ser usados apenas em um ambiente de teste .
_unsafeUnwrap leva um Result<T, E> e retorna um T quando o resultado é um Ok , caso contrário, ele lança um objeto personalizado.
_unsafeUnwrapErr requer um Result<T, E> e retorna um E quando o resultado é um Err , caso contrário, ele lança um objeto personalizado.
Dessa forma, você pode fazer algo como:
expect ( myResult . _unsafeUnwrap ( ) ) . toBe ( someExpectation ) No entanto, observe que as instâncias Result são comparáveis. Portanto, você não precisa necessariamente desembrulhá -los para afirmar as expectativas em seus testes. Então você também pode fazer algo assim:
import { ok } from 'neverthrow'
// ...
expect ( callSomeFunctionThatReturnsAResult ( "with" , "some" , "args" ) ) . toEqual ( ok ( someExpectation ) ) ; Por padrão, o valor jogado não contém um rastreamento de pilha. Isso ocorre porque a geração de rastreamento da pilha torna as mensagens de erro mais difíceis de entender. Se você deseja que os rastreios de pilha sejam gerados, ligue para _unsafeUnwrap e / ou _unsafeUnwrapErr com um objeto de configuração:
_unsafeUnwrapErr ( {
withStackTrace : true ,
} )
// ^ Now the error object will have a `.stack` property containing the current stackSe você achar esse pacote útil, considere me patrocinar ou simplesmente me comprar um café!
Embora o pacote seja chamado neverthrow , não leve isso literalmente. Estou simplesmente incentivando o desenvolvedor a pensar um pouco mais sobre a ergonomia e o uso de qualquer software que esteja escrevendo.
Throw e catching é muito semelhante ao uso de declarações goto - em outras palavras; Isso dificulta o raciocínio sobre seus programas. Em segundo lugar, usando throw , você pressupõe que o chamador de sua função está implementando catch . Esta é uma fonte conhecida de erros. Exemplo: um dev throw e outro dev usa a função sem conhecimento prévio de que a função lançará. Assim, e o caso de borda foi deixado sem conhecimento e agora você tem usuários infelizes, chefes, gatos, etc.
Com tudo isso dito, definitivamente existem bons casos de uso para jogar seu programa. Mas muito menos do que você imagina.
O projeto Neverhrow está disponível como código aberto nos termos da licença do MIT.