안녕 try/catch hello 오류 반환

코드가 런타임 중에 오류를 발생시키지 않기를 원하십니까? golang에서 영감을 받은 이 오류 반환 패턴을 사용하면 거의 근접할 수 있습니다. (나는 Go가 오랜 전통에서 복사했다고 가정한다.)

문제: Typescript와 javascript는 함수가 오류를 발생시킬 수 있음을 나타낼 방법이 없습니다. 따라서 일반적으로 충돌이 발생할 때까지 코드를 실행하거나 github 또는 node_modules에서 소스 코드를 추적하여(번들된 dist에는 일반적으로 축소되지 않은 헤더만 있기 때문에) 시도/잡을 위치를 파악해야 합니다.

오류 반환 패턴은 도구가 오류가 발생할 수 있는 위치를 추적하므로 해당 정보를 직접 암기하거나 찾을 필요가 없습니다. 오류 반환을 사용하면 처리되지 않은 오류가 타이프 스크립트 오류와 함께 편집기에 즉시 표시됩니다.

다음은 순차적인 분기 네트워킹 작업의 패턴 예입니다.

async function loadSong(id: string): Err | Song {
    const metadata = await loadMetadata(id)
    if (isErr(metadata)) return metadata // returns error!
    const mp3 = await loadMp3(id)
    if (!isErr(mp3)) return Song(metadata, mp3)
    // try ogg it might work
    const ogg = await loadOgg(id)
    if (!isErr(ogg)) return Song(metadata, ogg)
    // maybe the mirror has it?
    const mirrorMp3 = await loadMp3(id, { useMirror: true })
    if (!isErr(mirrorMp3)) return Song(metadata, mirrorMp3)
    return Err('all audio hosts failed')
}


(try-catch를 사용한 4단계 들여쓰기와 비교하면 꽤 좋습니다.)

그런 다음, 예를 들어 html 요소에서 오류를 포착하지 않고 이 함수를 사용하려고 하면 유형 오류가 발생합니다.

const addElm = document.body.appendChild
function playSong(id: string) {
    const song = loadSong(id)
    addElm(Player(song).play())
    // ↑ typescript error: .play() does not exist on type Err
    addElm(Metadata(song))
    // ↑ same error
}


패턴은 실패 사례를 설명하도록 강제합니다.

// Good code: won't runtime error and has no typescript errors
function playSong(id: string) {
    const song = loadSong(id)
    if (isErr(song)) {
        addElm(ErrorDiv('could not load song'))
        return
    }
    addElm(Player(song).play())
    addElm(Metadata(song))
}


다음을 방지하는 데 유용합니다.
  • 브라우저의 빈 화면 및 "버튼이 작동하지 않음"버그
  • 노드의 서버 시간 초과 및 잘못된 응답 버그
  • 시스템 스크립트가 중간 상태에서 실패하여 정크가 남음
  • 라이브러리 코드의 예기치 않은 오류

  • 당신은 라이브러리가 필요하지 않습니다



    이 패턴에 대한 모든 코드는 짧은 파일에 적합하며 필요에 따라 사용자 지정할 수 있습니다. 내 구현은 다음과 같습니다.

    // err.ts:
    
    const ERR = Symbol('ERR')
    type Err = {
        [ERR]: true
        error: unknown
        type?: ErrTypes
    }
    
    /** Optional addition if you want to handle errors differently based on their type */
    type ErrTypes = 'internet' | 'fileSystem' | 'badInput'
    
    export function isErr(x: unknown): x is Err {
        return typeof x === 'object' && x != null && ERR in x
    }
    
    export function Err(message: string, type?: string) {
        return { [ERR]: true, error: message, type: type }
    }
    
    /** Make an error-throwing function into a error-returning function */
    export async function tryFail<T>(
        f: (() => Promise<T>) | (() => T)
    ): Promise<T | Err> {
        try {
            return await f()
        } catch (e) {
            return { [ERR]: true, error: e }
        }
    }
    
    /** If you need to convert your error values back into throw/catch land */
    export function assertOk<T>(x: T | Err) {
        if (isErr(x)) throw Error(x.error)
    }
    


    가져오기 없이 항상 사용할 수 있도록 전역 패키지 범위에 넣는 것이 좋습니다.

    외부 라이브러리 및 stdlib에 오류 반환 패턴 사용



    예를 들어 설명하기 가장 쉬움

    /** Sometimes has error in runtime and crashes server */
    function getUserBad1(id: string) {
        const buf = readFileSync(`./users/${id}.json`)
        return JSON.parse(buf.toString())
    }
    
    /** Works but verbose: */
    function getUserBad2(id: string) {
        let buf: Buffer
        try {
            buf = readFileSync(`./users/${id}.json`)
        } catch (e) {
            console.warn('could not read file:', e)
            return null
        }
        let user: User
        try {
            user = JSON.parse(buf.toString())
            return user
        } catch (e) {
            console.warn('could not parse user file as json')
            return null
        }
    }
    
    /** tryFail pattern is best of both worlds */
    function getUser(id: string) {
        const buf = tryFail(() => readFileSync(`./users/${id}.json`))
        if (isErr(buf)) return buf
        return tryFail(() => JSON.parse(buf.toString()))
    }
    


    오류를 반환하도록 신뢰할 수 없는 함수를 래핑합니다.



    모든 곳에서 일부 라이브러리 함수를 사용 중이고 모든 곳에서 tryFail(()=>...)를 반복하는 데 지쳤다면(대량의 try-catch 체인을 이기는 경우에도) 오류 반환 논리로 라이브러리를 래핑하는 것이 도움이 될 수 있습니다.

    오류 라이브러리에 함수가 하나만 더 필요합니다.

    // err.ts:
    function errReturnify<In, Out>(
        f: (...args: In) => Out
    ): (...args: In) => Out | Err {
        return (...args: In) => {
            try {
                return f()
            } catch (e) {
                if (e instanceof Error) return Err(e.message)
                return Err(`unknown error in ${f.name}: ${JSON.stringify(e)}`)
            }
        }
    }
    


    그런 다음 이를 사용하여 래핑된 라이브러리를 만들 수 있습니다.

    // wrapped/fs.ts
    /** Wrap up error-throwing functions into error-returning ones */
    import {
        cpSync as cpSync_,
        mkdirSync as mkdirSync_,
        readFileSync as readFileSync_,
    } from 'fs'
    
    export const cpSync = errReturnify(cpSync_)
    export const mkdirSync = errReturnify(mkdirSync_)
    export const readFileSync = errReturnify(readFileSync_)
    
    // wrapped/JSON.ts
    export default JSON = {
        parse: errReturnify(JSON.parse),
        stringify: JSON.stringify,
    }
    


    그러면 완벽한 우아함과 신뢰성으로 라이브러리 코드를 사용할 수 있습니다.

    // server.ts
    import { readFileSync } from './wrapped/fs'
    import JSON from './wrapped/JSON'
    
    function getUser(id: string) {
        const buf = readFileSync(`./users/${id}.json`)
        if (isErr(buf)) return buf
        return JSON.parse(buf.toString())
    }
    


    약간의 불편함은 있지만 아무 함수나 래핑하는 데 두 줄이면 됩니다f . f를 몇 번 이상 사용한다면 노력할 가치가 있습니다.

    결론



    요컨대, 이 간단한 패턴은 TypeScript 네트워킹 코드에 널리 퍼져 있는 중첩된 try/catch가 있는 어색한 비어 있음let을 피하면서 TypeScript 코드를 훨씬 더 안정적으로 만들 수 있습니다.

    좋은 웹페이지 즐겨찾기