GithubHelp home page GithubHelp logo

iyegoroff / ts-railway Goto Github PK

View Code? Open in Web Editor NEW
11.0 2.0 0.0 2 MB

ROP flavoured Result & AsyncResult types

Home Page: https://iyegoroff.github.io/ts-railway/index.html

License: MIT License

TypeScript 100.00%
result railway-oriented-programming

ts-railway's Introduction

ts-railway

npm build publish codecov Type Coverage Libraries.io dependency status for latest release bundlejs npm

ROP flavoured Result & AsyncResult types. Based on Railway oriented programming article by Scott Wlaschin.

Getting started

$ npm i ts-railway

Overview

Main types


Functions

All mapping functions have at least two overloaded signatures - common (transform, result) => new_result and curried (transform) => (result) => new_result. Curried form is intended to be used with some piping function (e.g. pipe-ts).

Result AsyncResult
success โ†—๏ธ ๐Ÿšซ
failure โ†—๏ธ ๐Ÿšซ
map โ†—๏ธ โ†—๏ธ
mapError โ†—๏ธ โ†—๏ธ
flatMap โ†—๏ธ โ†—๏ธ
flatMapError โ†—๏ธ โ†—๏ธ
mapAsync ๐Ÿšซ โ†—๏ธ
mapAsyncError ๐Ÿšซ โ†—๏ธ
match โ†—๏ธ โ†—๏ธ
combine โ†—๏ธ โ†—๏ธ

Usage

Avoiding 'pyramid of doom'

Composing several functions with multiple arguments can be cumbersome and will lead to 'pyramid of doom' style of code:

const div = (a: number, b: number) /*: Result<number, 'div by zero'> */ =>
  b === 0 ? Result.failure('div by zero' as const) : Result.success(a / b)

const result = Result.map(
  (x: string) => [...x].reverse().join(''),
  Result.map(
    (x: number) => `${x}`,
    Result.map(
      (x: number) => x + 234,
      Result.mapError(
        (x: 'div by zero') => ({ divError: x } as const),
        Result.map((x) => x * 2, div(500, 1))
      )
    )
  )
)

expect(result).toEqual({
  tag: 'success',
  success: '4321'
})

This can be easily avoided when using curried forms of functions with a piping function:

import { pipeWith } from 'pipe-ts'

const result = pipeWith(
  div(500, 1),
  Result.mapError((x) => ({ divError: x } as const)),
  Result.map((x) => x * 2),
  Result.map((x) => x + 234),
  Result.map((x) => `${x}`),
  Result.map((x) => [...x].reverse().join(''))
)

expect(result).toEqual<typeof result>({
  tag: 'success',
  success: '4321'
})

Programming style

There are certain catches of railway oriented programming. Most of them are matter of program design quality. But in the context of TypeScript language, the most serious problem is the ability to completely discard the result of a function call (TypeScript/#8240, TypeScript/#8584). For example, in the following snippet possible parsing error will be discarded:

declare const obj: {
  parse: <T>(json: string) => Result<T, Error>
}

function foo() {
  obj.parse('][') // Result is discarded!
}

foo()

More sneaky error:

declare function updateUser(info: { name: string }): AsyncResult<undefined, Error>

declare const MyButton: {
  onClick: () => void
}

MyButton.onClick(
  () => updateUser({ name: 'username' }) // AsyncResult is covered with void and discarded!
)

These kind of problems can be minimized by using proper project configuration: setting "strict": true in tsconfig, prohibiting expression statements with functional/no-expression-statement rule from eslint-plugin-functional and banning void type with @typescript-eslint/ban-types rule from @typescript-eslint/eslint-plugin. tsconfig.json and .eslintrc files from this project could be used as a starting point.


Exception handling

ts-railway is intended to handle only domain errors and doesn't catch thrown exceptions and unhandled promise rejections. The common scenario to deal with exceptions is to catch them globally, log somehow and then decide whether to prevent an exception by fixing/changing the program or to convert that exception to domain error:

const errorHandler: OnErrorEventHandlerNonNull = (event) => {
  MyLoggingService.log(event)
}

window.onerror = errorHandler
window.onunhandledrejection = errorHandler

'Ecosystem'

Some packages compatible with ts-railway:

  • spectypes - fast, compiled, eval-free data validator/transformer
  • fetchmap - non-throwing fetch wrapper
  • ts-elmish - elmish architecture in typescript

ts-railway's People

Contributors

iyegoroff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

ts-railway's Issues

[suggestion] Utility function that wraps a function that can throw an error to Result<T,E>

I am curious, is there a reason why there is no utility function to wrap functions to a Result<T,E> as my canThrow does?

// Helper function
function canThrow<T>(func: () => T): Result<T, string> {
	try {
		return Result.success(func());
	} catch (e: any) {
		return Result.failure("Error: " + e.toString());
	}
}

Example use

	const result: Result<EditorState, string> = await pipeWith(
		canThrow(() => Buffer.from(q)),
		AsyncResult.flatMap(x => ungzipBuffer(x) as AsyncResult<string, string>),
		AsyncResult.map((x: string) => JSON.parse(x)),
		AsyncResult.map((x: RawDraftContentState) => convertFromRaw(x)),
		AsyncResult.map((x: ContentState) => EditorState.createWithContent(x)),
	)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.