오류 클래스가 있는 NodeJS의 더 나은 오류 처리
게시 됨: 2022-03-10error
클래스 패턴과 이를 사용하여 애플리케이션 전체에서 오류를 더 효율적이고 효율적으로 처리하는 방법을 설명합니다.오류 처리는 실제로 받을 가치가 있는 충분한 관심을 받지 못하는 소프트웨어 개발 부분 중 하나입니다. 그러나 강력한 애플리케이션을 구축하려면 오류를 적절하게 처리해야 합니다.
오류를 적절하게 처리하지 않고도 NodeJS를 사용할 수 있지만 NodeJS의 비동기 특성으로 인해 부적절한 처리 또는 오류는 특히 애플리케이션을 디버깅할 때 곧 고통을 유발할 수 있습니다.
계속 진행하기 전에 오류 클래스를 활용하는 방법에 대해 논의할 오류 유형을 지적하고 싶습니다.
작동 오류
프로그램 실행 중에 발견된 오류입니다. 운영 오류는 버그가 아니며 데이터베이스 서버 시간 초과 또는 사용자가 입력 필드에 SQL 쿼리를 입력하여 SQL 삽입을 시도하기로 결정하는 것과 같은 여러 외부 요인 중 하나 또는 조합으로 인해 때때로 발생할 수 있습니다.
다음은 작동 오류의 더 많은 예입니다.
- 데이터베이스 서버에 연결하지 못했습니다.
- 사용자의 잘못된 입력(서버가
400
응답 코드로 응답) - 요청 시간 초과;
- 리소스를 찾을 수 없습니다(서버가 404 응답 코드로 응답).
- 서버는
500
응답으로 반환됩니다.
Operational Errors에 대해 간단히 논의하는 것도 주목할 가치가 있습니다.
프로그래머 오류
이것은 코드를 변경하여 해결할 수 있는 프로그램의 버그입니다. 이러한 유형의 오류는 코드가 깨져서 발생하기 때문에 처리할 수 없습니다. 이러한 오류의 예는 다음과 같습니다.
- 정의되지 않은 개체의 속성을 읽으려고 합니다.
const user = { firstName: 'Kelvin', lastName: 'Omereshone', } console.log(user.fullName) // throws 'undefined' because the property fullName is not defined
- 콜백 없이 비동기 함수를 호출하거나 호출합니다.
- 숫자가 예상되는 문자열을 전달합니다.
이 문서는 NodeJS의 Operational Error 처리 에 관한 것입니다. NodeJS의 오류 처리는 다른 언어의 오류 처리와 크게 다릅니다. 이것은 JavaScript의 비동기 특성과 오류가 있는 JavaScript의 개방성 때문입니다. 설명하겠습니다:
JavaScript에서 error
클래스의 인스턴스는 던질 수 있는 유일한 것이 아닙니다. 이 개방성은 다른 언어에서 허용되지 않는 모든 데이터 유형을 말 그대로 던질 수 있습니다.
예를 들어 JavaScript 개발자는 다음과 같이 오류 개체 인스턴스 대신 숫자를 입력하기로 결정할 수 있습니다.
// bad throw 'Whoops :)'; // good throw new Error('Whoops :)')
다른 데이터 유형을 던질 때 문제가 발생하지 않을 수도 있지만 그렇게 하면 디버깅에 필요한 스택 추적 및 Error 개체가 노출하는 기타 속성을 얻지 못하기 때문에 디버깅 시간이 더 어려워집니다.
Error 클래스 패턴과 NodeJS에서 오류 처리를 위한 훨씬 더 나은 방법을 살펴보기 전에 오류 처리의 몇 가지 잘못된 패턴을 살펴보겠습니다.
잘못된 오류 처리 패턴 #1: 콜백의 잘못된 사용
실제 시나리오 : 코드는 반환할 것으로 예상하는 결과를 얻기 위해 콜백이 필요한 외부 API에 따라 다릅니다.
아래 코드 스니펫을 살펴보겠습니다.
'use strict'; const fs = require('fs'); const write = function () { fs.mkdir('./writeFolder'); fs.writeFile('./writeFolder/foobar.txt', 'Hello World'); } write();
NodeJS 8 이상까지는 위의 코드가 합법적이었고 개발자는 단순히 명령을 실행하고 잊어버렸습니다. 즉, 개발자는 이러한 함수 호출에 대한 콜백을 제공할 필요가 없으므로 오류 처리를 생략할 수 있습니다. writeFolder
가 생성되지 않으면 어떻게 됩니까? writeFile
에 대한 호출이 이루어지지 않으며 이에 대해 아무 것도 알 수 없습니다. 두 번째 명령이 다시 시작될 때 첫 번째 명령이 완료되지 않았을 수 있기 때문에 경쟁 조건이 발생할 수도 있습니다.
경쟁 조건을 해결하여 이 문제를 해결해 보겠습니다. 첫 번째 명령 mkdir
에 콜백을 제공하여 두 번째 명령으로 디렉토리에 쓰기 전에 디렉토리가 실제로 존재하는지 확인함으로써 그렇게 할 것입니다. 따라서 우리의 코드는 아래와 같을 것입니다.
'use strict'; const fs = require('fs'); const write = function () { fs.mkdir('./writeFolder', () => { fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); }); } write();
경쟁 조건을 해결했지만 아직 완료되지 않았습니다. 첫 번째 명령에 콜백을 사용했지만 writeFolder
폴더가 생성되었는지 여부를 알 수 없기 때문에 코드는 여전히 문제가 있습니다. 폴더가 생성되지 않은 경우 두 번째 호출은 다시 실패하지만 여전히 오류를 다시 무시했습니다. 우리는 다음을 통해 이 문제를 해결합니다.
콜백을 사용한 오류 처리
콜백으로 오류를 올바르게 처리하려면 항상 오류 우선 접근 방식을 사용해야 합니다. 이것이 의미하는 바는 반환된 데이터(있는 경우)를 사용하기 전에 먼저 함수에서 반환된 오류가 있는지 확인해야 한다는 것입니다. 이 작업을 수행하는 잘못된 방법을 살펴보겠습니다.
'use strict'; // Wrong const fs = require('fs'); const write = function (callback) { fs.mkdir('./writeFolder', (err, data) => { if (data) fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); else callback(err) }); } write(console.log);
때때로 호출하는 API가 값을 반환하지 않거나 잘못된 값을 유효한 반환 값으로 반환할 수 있기 때문에 위의 패턴이 잘못된 것입니다. 이렇게 하면 함수 또는 API를 성공적으로 호출한 것 같더라도 오류가 발생하게 됩니다.
위의 패턴은 사용법이 오류를 잡아먹기 때문에 좋지 않습니다(오류가 발생했을지라도 오류가 호출되지 않음). 또한 이러한 종류의 오류 처리 패턴으로 인해 코드에서 어떤 일이 일어나고 있는지 전혀 알 수 없습니다. 따라서 위의 코드에 대한 올바른 방법은 다음과 같습니다.
'use strict'; // Right const fs = require('fs'); const write = function (callback) { fs.mkdir('./writeFolder', (err, data) => { if (err) return callback(err) fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); }); } write(console.log);
잘못된 오류 처리 패턴 #2: 잘못된 약속 사용
실제 시나리오 : Promises를 발견했고 콜백 지옥 때문에 콜백보다 훨씬 낫다고 생각하고 코드 기반이 의존하는 일부 외부 API를 약속하기로 결정했습니다. 또는 외부 API 또는 fetch() 함수와 같은 브라우저 API에서 약속을 사용하고 있습니다.
요즘에는 NodeJS 코드베이스에서 콜백을 사용하지 않고 약속을 사용합니다. 이제 약속을 사용하여 예제 코드를 다시 구현해 보겠습니다.
'use strict'; const fs = require('fs').promises; const write = function () { return fs.mkdir('./writeFolder').then(() => { fs.writeFile('./writeFolder/foobar.txt', 'Hello world!') }).catch((err) => { // catch all potential errors console.error(err) }) }
위의 코드를 현미경으로 살펴보겠습니다. fs.mkdir
약속을 다른 약속 체인(fs.writeFile에 대한 호출)으로 분기하여 그 약속 호출을 처리하지도 않고 분기하고 있음을 알 수 있습니다. 더 나은 방법은 다음과 같다고 생각할 수 있습니다.
'use strict'; const fs = require('fs').promises; const write = function () { return fs.mkdir('./writeFolder').then(() => { fs.writeFile('./writeFolder/foobar.txt', 'Hello world!').then(() => { // do something }).catch((err) => { console.error(err); }) }).catch((err) => { // catch all potential errors console.error(err) }) }
그러나 위의 내용은 확장되지 않습니다. 이는 호출할 약속 체인이 더 많으면 약속이 해결하기 위해 만들어진 콜백 지옥과 유사한 결과를 초래할 것이기 때문입니다. 이것은 우리 코드가 계속해서 오른쪽으로 들여쓰기를 한다는 것을 의미합니다. 우리는 우리 손에 지옥을 약속할 것입니다.
콜백 기반 API 약속
대부분의 경우 해당 API의 오류를 더 잘 처리하기 위해 자체적으로 콜백 기반 API를 약속하고 싶을 것입니다. 그러나 이것은 정말 쉬운 일이 아닙니다. 그 이유를 설명하기 위해 아래의 예를 들어보겠습니다.
function doesWillNotAlwaysSettle(arg) { return new Promise((resolve, reject) => { doATask(foo, (err) => { if (err) { return reject(err); } if (arg === true) { resolve('I am Done') } }); }); }
위에서 arg
가 true
가 아니고 doATask
함수 호출로 인한 오류가 없으면 이 약속은 중단되어 애플리케이션의 메모리 누수입니다.
프라미스의 삼킨 동기화 오류
Promise 생성자를 사용하는 데는 몇 가지 어려움이 있습니다. 이러한 어려움 중 하나는 다음과 같습니다. 해결되거나 거부되는 즉시 다른 상태를 얻을 수 없습니다. 이는 약속이 보류 중이거나 해결/거부된 단일 상태만 얻을 수 있기 때문입니다. 이것은 우리가 약속에 사각지대를 가질 수 있음을 의미합니다. 이것을 코드로 보자:
function deadZonePromise(arg) { return new Promise((resolve, reject) => { doATask(foo, (err) => { resolve('I'm all Done'); throw new Error('I am never reached') // Dead Zone }); }); }
위의 내용에서 약속이 해결되자마자 다음 라인은 데드존이며 절대 도달하지 않을 것임을 알 수 있습니다. 이것은 당신의 약속에서 수행되는 다음 동기식 오류 처리가 그냥 삼켜지고 절대 던져지지 않을 것임을 의미합니다.
실제 사례
위의 예는 잘못된 오류 처리 패턴을 설명하는 데 도움이 됩니다. 실생활에서 볼 수 있는 종류의 문제를 살펴보겠습니다.
실제 예제 #1 — 오류를 문자열로 변환
시나리오 : API에서 반환된 오류가 실제로 충분하지 않다고 판단하여 여기에 자신의 메시지를 추가하기로 결정했습니다.
'use strict'; function readTemplate() { return new Promise(() => { databaseGet('query', function(err, data) { if (err) { reject('Template not found. Error: ', + err); } else { resolve(data); } }); }); } readTemplate();
위의 코드에서 무엇이 잘못되었는지 살펴봅시다. 위에서 우리는 개발자가 반환된 오류를 "Template not found" 문자열과 연결하여 databaseGet
API에서 발생하는 오류를 개선하려고 시도하고 있음을 알 수 있습니다. 이 접근 방식은 연결이 완료되었을 때 개발자가 반환된 오류 개체에 대해 암시적으로 toString
을 실행하기 때문에 많은 단점이 있습니다. 이런 식으로 그는 오류에 의해 반환된 추가 정보를 잃게 됩니다(스택 추적과 작별 인사). 따라서 개발자가 지금 가지고 있는 것은 디버깅할 때 유용하지 않은 문자열입니다.
더 나은 방법은 오류를 있는 그대로 유지하거나 생성한 다른 오류로 감싸고 databaseGet 호출에서 발생한 오류를 속성으로 첨부하는 것입니다.
실제 예제 #2: 오류를 완전히 무시하기
시나리오 : 아마도 사용자가 애플리케이션에 가입할 때 오류가 발생하면 오류를 포착하고 사용자 정의 메시지를 표시하고 싶지만 디버깅 목적으로 로깅하지 않고 포착된 오류를 완전히 무시했을 수 있습니다.
router.get('/:id', function (req, res, next) { database.getData(req.params.userId) .then(function (data) { if (data.length) { res.status(200).json(data); } else { res.status(404).end(); } }) .catch(() => { log.error('db.rest/get: could not get data: ', req.params.userId); res.status(500).json({error: 'Internal server error'}); }) });
위에서 우리는 오류가 완전히 무시되고 데이터베이스 호출이 실패한 경우 코드가 사용자에게 500을 보내는 것을 볼 수 있습니다. 그러나 실제로 데이터베이스 장애의 원인은 사용자가 보낸 잘못된 데이터일 수 있으며 이는 상태 코드 400의 오류입니다.
위의 경우 개발자인 당신이 무엇이 잘못되었는지 알지 못하기 때문에 디버깅 공포에 빠지게 될 것입니다. 500 내부 서버 오류가 항상 발생하기 때문에 사용자는 적절한 보고서를 제공할 수 없습니다. 고용주의 시간과 돈을 낭비하는 것과 같은 문제를 찾는 데 시간을 낭비하게 될 것입니다.
실제 예제 #3: API에서 발생한 오류를 수락하지 않음
시나리오 : 사용 중인 API에서 오류가 발생했지만 해당 오류를 수락하지 않고 대신 디버깅 목적으로 쓸모없게 만드는 방식으로 오류를 마샬링하고 변환합니다.
아래의 다음 코드 예를 살펴보세요.
async function doThings(input) { try { validate(input); try { await db.create(input); } catch (error) { error.message = `Inner error: ${error.message}` if (error instanceof Klass) { error.isKlass = true; } throw error } } catch (error) { error.message = `Could not do things: ${error.message}`; await rollback(input); throw error; } }
디버깅 공포로 이어질 위의 코드에서 많은 일이 일어나고 있습니다. 한 번 보자:
-
try/catch
블록 래핑: 위에서 보면 우리가try/catch
블록을 래핑하는 것을 볼 수 있는데 이는 매우 나쁜 생각입니다. 우리는 일반적으로 오류를 처리해야 하는 표면을 최소화하기try/catch
블록의 사용을 줄이려고 노력합니다(DRY 오류 처리로 생각하십시오). - 우리는 또한 좋은 생각이 아닌 개선 시도에서 오류 메시지를 조작합니다.
- 오류가
Klass
유형의 인스턴스인지 확인하고 있으며 이 경우 오류isKlass
의 부울 속성을 truev로 설정하고 있습니다(그러나 해당 검사가 통과하면 오류는Klass
유형임). - 또한 코드 구조에서 오류가 발생했을 때 데이터베이스에 도달하지 않았을 수도 있는 경향이 높기 때문에 데이터베이스를 너무 일찍 롤백하고 있습니다.
다음은 위의 코드를 작성하는 더 좋은 방법입니다.
async function doThings(input) { validate(input); try { await db.create(input); } catch (error) { try { await rollback(); } catch (error) { logger.log('Rollback failed', error, 'input:', input); } throw error; } }
위의 스니펫에서 우리가 하고 있는 일을 분석해 봅시다.
- 우리는 하나의
try/catch
블록을 사용하고 있으며 catch 블록에서만 다른try/catch
블록을 사용하고 있습니다. 이 블록은 롤백 기능에 문제가 발생하여 이를 기록하는 경우에 대비하여 보호 역할을 합니다. - 마지막으로 원래 수신된 오류가 발생하여 해당 오류에 포함된 메시지가 손실되지 않습니다.
테스트
우리는 대부분 우리의 코드를 테스트하기를 원합니다(수동 또는 자동으로). 그러나 대부분의 경우 우리는 긍정적인 것에 대해서만 테스트하고 있습니다. 강력한 테스트를 위해서는 오류 및 엣지 케이스도 테스트해야 합니다. 이러한 부주의는 버그가 프로덕션 단계에 들어가는 원인이 되며, 이로 인해 디버깅 시간이 더 많이 소요됩니다.
팁 : 항상 긍정적인 것(엔드포인트에서 상태 코드 200 가져오기)뿐만 아니라 모든 오류 사례와 모든 엣지 사례도 테스트해야 합니다.
실제 사례 #4: 처리되지 않은 거부
이전에 promise를 사용한 적이 있다면 unhandled rejections
에 직면했을 것입니다.
다음은 처리되지 않은 거부에 대한 간단한 입문서입니다. 처리되지 않은 거부는 처리되지 않은 약속 거부입니다. 이것은 약속이 거부되었지만 코드가 계속 실행됨을 의미합니다.
처리되지 않은 거부로 이어지는 일반적인 실제 사례를 살펴보겠습니다.
'use strict'; async function foobar() { throw new Error('foobar'); } async function baz() { throw new Error('baz') } (async function doThings() { const a = foobar(); const b = baz(); try { await a; await b; } catch (error) { // ignore all errors! } })();
위의 코드는 언뜻 보기에 오류가 발생하기 쉽지 않은 것처럼 보일 수 있습니다. 그러나 자세히 살펴보면 결함이 보이기 시작합니다. 설명하겠습니다 a
거부되면 어떻게 됩니까? 이는 await b
에 도달하지 않았음을 의미하며 처리되지 않은 거부를 의미합니다. 가능한 해결책은 두 약속 모두에 Promise.all
을 사용하는 것입니다. 따라서 코드는 다음과 같이 읽힙니다.
'use strict'; async function foobar() { throw new Error('foobar'); } async function baz() { throw new Error('baz') } (async function doThings() { const a = foobar(); const b = baz(); try { await Promise.all([a, b]); } catch (error) { // ignore all errors! } })();
다음은 처리되지 않은 약속 거부 오류로 이어지는 또 다른 실제 시나리오입니다.
'use strict'; async function foobar() { throw new Error('foobar'); } async function doThings() { try { return foobar() } catch { // ignoring errors again ! } } doThings();
위의 코드 조각을 실행하면 처리되지 않은 약속 거부가 발생하며 그 이유는 다음과 같습니다. 명확하지 않지만 try/catch
로 처리하기 전에 약속(foobar)을 반환하고 있습니다. 우리가 해야 할 일은 try/catch
로 처리할 약속을 기다리는 것이므로 코드가 다음과 같이 읽힐 것입니다.
'use strict'; async function foobar() { throw new Error('foobar'); } async function doThings() { try { return await foobar() } catch { // ignoring errors again ! } } doThings();
부정적인 일 정리하기
잘못된 오류 처리 패턴과 가능한 수정 사항을 보았으므로 이제 Error 클래스 패턴과 NodeJS에서 잘못된 오류 처리 문제를 해결하는 방법에 대해 알아보겠습니다.
오류 클래스
이 패턴에서 우리는 명시적으로 던진 응용 프로그램의 모든 오류가 이 클래스에서 상속될 것임을 알고 있는 ApplicationError
클래스로 응용 프로그램을 시작합니다. 따라서 다음 오류 클래스로 시작합니다.
-
ApplicationError
이것은 다른 모든 오류 클래스의 조상입니다. 즉, 다른 모든 오류 클래스는 이 클래스에서 상속합니다. -
DatabaseError
데이터베이스 작업과 관련된 모든 오류는 이 클래스에서 상속됩니다. -
UserFacingError
응용 프로그램과 상호 작용하는 사용자의 결과로 생성된 모든 오류는 이 클래스에서 상속됩니다.
다음은 error
클래스 파일의 모양입니다.
'use strict'; // Here is the base error classes to extend from class ApplicationError extends Error { get name() { return this.constructor.name; } } class DatabaseError extends ApplicationError { } class UserFacingError extends ApplicationError { } module.exports = { ApplicationError, DatabaseError, UserFacingError }
이 접근 방식을 통해 애플리케이션에서 발생한 오류를 구별할 수 있습니다. 따라서 이제 잘못된 요청 오류(잘못된 사용자 입력) 또는 찾을 수 없는 오류(리소스를 찾을 수 없음)를 처리하려는 경우 UserFacingError
인 기본 클래스에서 상속할 수 있습니다(아래 코드 참조).
const { UserFacingError } = require('./baseErrors') class BadRequestError extends UserFacingError { constructor(message, options = {}) { super(message); // You can attach relevant information to the error instance // (eg. the username) for (const [key, value] of Object.entries(options)) { this[key] = value; } } get statusCode() { return 400; } } class NotFoundError extends UserFacingError { constructor(message, options = {}) { super(message); // You can attach relevant information to the error instance // (eg. the username) for (const [key, value] of Object.entries(options)) { this[key] = value; } } get statusCode() { return 404 } } module.exports = { BadRequestError, NotFoundError }
error
클래스 접근 방식의 이점 중 하나는 이러한 오류 중 하나(예: NotFoundError
)가 발생하면 이 코드베이스를 읽는 모든 개발자가 이 시점에서 무슨 일이 일어나고 있는지 이해할 수 있다는 것입니다. ).
해당 오류를 인스턴스화하는 동안에도 각 오류 클래스에 특정한 여러 속성을 전달할 수 있습니다.
또 다른 주요 이점은 항상 오류 클래스의 일부인 속성을 가질 수 있다는 것입니다. 예를 들어 UserFacing 오류를 수신하는 경우 statusCode가 항상 이 오류 클래스의 일부라는 것을 알게 되므로 이제 바로 사용할 수 있습니다. 나중에 코드.
오류 클래스 활용에 대한 팁
- 각 오류 클래스에 대해 고유한 모듈(개인용일 수 있음)을 만들어 애플리케이션에서 간단히 가져와서 모든 곳에서 사용할 수 있습니다.
- 관심 있는 오류만 던집니다(오류 클래스의 인스턴스인 오류). 이렇게 하면 오류 클래스가 유일한 진실 소스이며 여기에는 애플리케이션을 디버그하는 데 필요한 모든 정보가 포함되어 있다는 것을 알 수 있습니다.
- 추상 오류 모듈을 갖는 것은 이제 우리 애플리케이션이 던질 수 있는 오류에 관한 모든 필요한 정보가 한 곳에 있다는 것을 알기 때문에 매우 유용합니다.
- 레이어의 오류를 처리합니다. 모든 곳에서 오류를 처리하는 경우 추적하기 어려운 오류 처리에 대한 일관성 없는 접근 방식을 갖게 됩니다. 계층이란 데이터베이스, 표현/고속/HTTP 계층 등을 의미합니다.
코드에서 오류 클래스가 어떻게 보이는지 봅시다. 다음은 익스프레스의 예입니다.
const { DatabaseError } = require('./error') const { NotFoundError } = require('./userFacingErrors') const { UserFacingError } = require('./error') // Express app.get('/:id', async function (req, res, next) { let data try { data = await database.getData(req.params.userId) } catch (err) { return next(err); } if (!data.length) { return next(new NotFoundError('Dataset not found')); } res.status(200).json(data) }) app.use(function (err, req, res, next) { if (err instanceof UserFacingError) { res.sendStatus(err.statusCode); // or res.status(err.statusCode).send(err.errorCode) } else { res.sendStatus(500) } // do your logic logger.error(err, 'Parameters: ', req.params, 'User data: ', req.user) });
위에서 우리는 Express가 모든 오류를 한 곳에서 처리할 수 있는 전역 오류 처리기를 노출한다는 점을 활용하고 있습니다. 오류를 처리하는 위치에서 next()
호출을 볼 수 있습니다. 이 호출은 app.use
섹션에 정의된 핸들러에 오류를 전달합니다. express는 async/await를 지원하지 않기 때문에 try/catch
블록을 사용하고 있습니다.
따라서 위의 코드에서 오류를 처리하려면 발생한 오류가 UserFacingError
인스턴스인지 확인하기만 하면 자동으로 오류 개체에 statusCode가 있다는 것을 알고 이를 사용자에게 보냅니다(원할 수도 있습니다. 클라이언트에 전달할 수 있는 특정 오류 코드도 포함해야 합니다.
또한 이 패턴( error
클래스 패턴)에서 명시적으로 던지지 않은 다른 모든 오류는 500
오류라는 것을 알 수 있습니다. 이는 응용 프로그램에서 해당 오류를 명시적으로 던지지 않았음을 의미하는 예상치 못한 것이기 때문입니다. 이런 식으로 애플리케이션에서 발생하는 오류 유형을 구별할 수 있습니다.
결론
애플리케이션에서 적절한 오류 처리는 밤에 잠을 잘 자게 하고 디버그 시간을 절약할 수 있습니다. 다음은 이 기사에서 취해야 할 몇 가지 핵심 요점입니다.
- 애플리케이션에 대해 특별히 설정된 오류 클래스를 사용하십시오.
- 추상 오류 처리기를 구현합니다.
- 항상 async/await를 사용하십시오.
- 오류를 표현하십시오.
- 사용자는 필요한 경우 약속합니다.
- 적절한 오류 상태 및 코드를 반환합니다.
- 약속 후크를 사용하십시오.
유용한 프론트엔드 및 UX 비트, 일주일에 한 번 제공됩니다.
작업을 더 잘 수행하는 데 도움이 되는 도구가 있습니다. 이메일을 통해 Vitaly의 스마트 인터페이스 디자인 체크리스트 PDF 를 구독하고 받으십시오.
프론트엔드 및 UX에서. 190,000명의 사람들이 신뢰합니다.