프라미스 API

Promise.all( promises )

주어진 배열에 있는 모든 프로미스가 이행될 때까지 기다렸다가 그 결과값을 주어진 배열의 순서에 따라 반환합니다.

 예시

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert); // [1,2,3] 반환

 

배열 속 프로미스중 하나라도 실패를 하면 promise.all  전체가 거부되고 에러가 반환됩니다.

   예시

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: 에러 발생!

Promise.allSettled( promises )

최근에 추가된 문법

Promise.all 과 다르게 여러개의 요청 중에 하나가 실패해도 다른 성공된 결과와 함께 반환

여러 요청 중 하나가 실패해도 다른 요청 결과는 여전히 필요한 경우에 유용한 메서드

     

요청 예시

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/Violet-Bora-Lee',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { // (*)
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });
  });

 

결과 예시

[
  {status: 'fulfilled', value: ...응답...},
  {status: 'fulfilled', value: ...응답...},
  {status: 'rejected', reason: ...에러 객체...}
]

 

만약에 브라우저가 promise.allSettled 를 지원하지 않는다면 폴리필을 구현

   폴리필? 브라우저에서 지원하지 않는 코드를 사용한 가능한 코드나 프롤그인으로 변환 한 코드를 의미합니다.

if(!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
      status: 'fulfilled',
      value
    }), reason => ({
      status: 'rejected',
      reason
    }))));
  };
}

 

Promise.race( promises )

Promise.all과 비슷하지만 가장 먼저 처리되는 프라미스의 결과(혹은 에러)를 반환합니다.

 예시

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1

 

Promise.resolve( value )

주어진 값을 이용하여 이행 상태의 프라미스를 만듭니다.

Promise.reject( error )

주어진 에러를 사용해 거부 상태의 프라미스를 만듭니다

 

* 메서드 Promise.resolve Promise.reject async/await 문법이 생긴 후로 쓸모없어졌기 때문에 근래에는 거의 사용하지 않습니다.

 

참고자료

https://ko.javascript.info/async

프라미스와 에러 핸들링

암시적 try…catch

체인 마지막의 .catch는 try..catch와 유사한 역할을 합니다.

 .then 핸들러를 원하는 만큼 사용하다 마지막에 .catch 하나만 붙이면 .then 핸들러에서 발생한 모든 에러를 처리할 수 있습니다.

 

.catch 는 프라미스에서 발생한 모든 에러를 다룹니다. reject()가 호출되거나 에러가 던져지면 .catch에서 이를 처리합니다.

 

예시

new Promise((resolve, reject) => {
  resolve("OK");
}).then((result) => {
  throw new Error("에러 발생!"); // 프라미스가 거부됨
}).catch(alert); // Error: 에러 발생!
new Promise((resolve, reject) => {
  resolve("OK");
}).then((result) => {
  blabla(); // 존재하지 않는 함수
}).catch(alert); // ReferenceError: blabla is not defined

예시에서 보듯 .catch는 첫번째 핸들러일 필요가 없고 하나 혹은 여러 개의 .then 뒤에 올 수 있습니다.

 

사이트에는 아무런 문제가 없지만 응답으로 받은 JSON의 형식이 잘못된 경우를 살펴봅시다.

가장 쉬운 에러 처리 방법은 체인 끝에 .catch 를 붙이는 것입니다.

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise((resolve, reject) => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  }))
  .catch(error => alert(error.message));

 정상적인 경우라면 .catch 는 절대 트리거 되지 않습니다. 하지만, 네트워크 문제, 잘못된 형식의 JSON 등으로 인해 위쪽 프라미스 중 하나라도 거부 되면 .catch 에서 에러를 잡게 됩니다.

 

다시 던지기

 try..catch에선 에러를 분석하고, 처리할 수 없는 에러라 판단되면 에러를 다시 던질 때가 있습니다.

프라미스에도 유사한 일을 할 수 있습니다.

.catch 안에서 throw 를 사용하면 제어 흐름이 가장 가까운 곳에 있는 에러 핸들러로 넘어갑니다.

여기서 에러가 성공적으로 처리되면 바로 다음 .then 핸들러로 제어 흐름이 넘어가 실행이 이어집니다.

 

예시

new Promise((resolve, reject) => {

  throw new Error("에러 발생!");

}).catch(function(error) {

  alert("에러가 잘 처리되었습니다. 정상적으로 실행이 이어집니다.");

}).then(() => alert("다음 핸들러가 실행됩니다."));

    - catch 에서 정상적으로 종료되었기 때문에 다음 성공핸들러 .then 이 호출 되것을 확인 가능합니다.

 

catch 에서 에러를 처리하지 못한 예시

 // 실행 순서: catch -> catch
new Promise((resolve, reject) => {

  throw new Error("에러 발생!");

}).catch(function(error) { // (*)

  if (error instanceof URIError) {
    // 에러 처리
  } else {
    alert("처리할 수 없는 에러");

    throw error; // 에러 다시 던지기
  }

}).then(function() {
  /* 여기는 실행되지 않습니다. */
}).catch(error => { // (**)

  alert(`알 수 없는 에러가 발생함: ${error}`);
  // 반환값이 없음 => 실행이 계속됨

});

 

처리되지 못한 거부

에러를 처리해줄 핸들러가 없으면 에러가 갇혀버리게 됩니다.

  예시

new Promise(function() {
  noSuchFunction(); // 존재하지 않는 함수를 호출하기 때문에 에러가 발생함
})
  .then(() => {
    // 성공상태의 프라미스를 처리하는 핸들러. 한 개 혹은 여러 개가 있을 수 있음
  }); // 끝에 .catch가 없음!

 이런 경우 스크립트가 죽고 자바스크립트 엔진은 전역 에러를 생성합니다.

이떄 콘솔 창을 열어 확인하면 전역 에러를 확인 가능합니다.

브라우저 환경에서는 이러한 에러를 unhandledrejection 이벤트로 처리할 수 있습니다.

window.addEventListener('unhandledrejection', function(event) {
  // unhandledrejection 이벤트엔 두 개의 특수 프로퍼티가 있습니다.
  alert(event.promise); // [object Promise] - 에러를 생성하는 프라미스
  alert(event.reason); // Error: 에러 발생! - 처리하지 못한 에러 객체
});

new Promise(function() {
  throw new Error("에러 발생!");
}); // 에러를 처리할 수 있는 .catch 핸들러가 없음

     이런 에러는 회복할 수 없기 때문에 최선의 방법은 사용자에게 문제 상황을 알리고 가능하다면 서버에 에러 정보를 보내는 것입니다.

 

.catch 가 실행되지 않는 예제

new Promise(function(resolve, reject) {
  setTimeout(() => {
    throw new Error("에러 발생!");
  }, 1000);
}).catch(alert);

  위 예시에서는 executer 가 실행되는 동안이 아니라 나중에 에러가 발생합니다. 그래서 프라미스는 에러를 처리 할 수 없습니다.

 

참고자료

https://ko.javascript.info/async

프라미스 체이닝(promise chaining)

result.then 핸들러의 체인을 통해 전달된다는 점에서 착안한 아이디어 

 

예시

- fetch 이용

fetch('/article/promise-chaining/user.json')
  // 원격 서버가 응답하면 .then 아래 코드가 실행됩니다.
  .then(function(response) {
    // response.text()는 응답 텍스트 전체가 다운로드되면
    // 응답 텍스트를 새로운 이행 프라미스를 만들고, 이를 반환합니다.
    return response.text();
  })
  .then(function(text) {
    // 원격에서 받아온 파일의 내용
    alert(text); // {"name": "Violet-Bora-Lee", "isAdmin": true}
  });

 

-- 간단하게 변경

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => alert(user.name));

 

- fetch  + 함수

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise(function(resolve, reject) { // (*)
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser); // (**)
    }, 3000);
  }))
  // 3초 후 동작함
  .then(githubUser => alert(`${githubUser.name}의 이미지를 성공적으로 출력하였습니다.`))

-- 간략화 ( 함수단위로 분해)

function loadJson(url) {
  return fetch(url)
    .then(response => response.json());
}

function loadGithubUser(name) {
  return fetch(`https://api.github.com/users/${name}`)
    .then(response => response.json());
}

function showAvatar(githubUser) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  });
}

// 함수를 이용하여 다시 동일 작업 수행
loadJson('/article/promise-chaining/user.json')
  .then(user => loadGithubUser(user.name))
  .then(showAvatar)
  .then(githubUser => alert(`Finished showing ${githubUser.name}`));
  // ...

 

참고자료

https://ko.javascript.info/async

프라미스 

promise 객체 기본 구조

let promise = new Promise(function(resolve, reject) {
  // executor (제작 코드)
});

  • executer 는 자동으로 실행, 이 부분에서 코드로 작성한 원하는 작업이 처리됩니다.
  • executer 의 인수 resolve 와 reject 는 자바스크립트에서 자제 제공하는 콜백입니다.
    • resolve : 작업이 성공적으로 끝난 경우 그 결과를 나타낸는 value 와 함께 호출됩니다.
    • reject : 에러 발생시 에러객체를 나타내는 error와 함께 호출
  • 프로미스의 특징 : 성공또는 실패 둘 중 하나만 존재

예시

let promise = new Promise(function(resolve, reject) {
  resolve("완료");

  reject(new Error("…")); // 무시됨
  setTimeout(() => resolve("…")); // 무시됨
});

 

프라미스 소비자  then, catch, finally

then

- 첫 번째 인수는 프라미스가 fullfilled 되었을 때 실행 -> 실행 결과

- 두 번째 인수는 프라미스가 refuse 되었을 때 실행 -> 에러 호출

promise.then(
  function(result) { /* 결과(result)를 다룹니다 */ },
  function(error) { /* 에러(error)를 다룹니다 */ }
);

 

reject 예시

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

// reject 함수는 .then의 두 번째 함수를 실행합니다.
promise.then(
  result => alert(result), // 실행되지 않음
  error => alert(error) // 1초 후 "Error: 에러 발생!"을 출력
);

 

resolve 예시

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("완료!"), 1000);
});

// resolve 함수는 .then의 첫 번째 함수(인수)를 실행합니다.
promise.then(
  result => alert(result), // 1초 후 "완료!"를 출력
  error => alert(error) // 실행되지 않음
);

 

Catch

에러만 다루고 싶은 경우 사용 try {...} catch{...} 에서 catch 와 같은 역할

.then() 을 다 나열한 후 마지막에 사용되는 소비자

 

Finally

결과가 어떻든 마무리가 필요하면 finally 가 유용

finally 에선 절차를 마무리하는 보편적 동작을 수행하기 때문에  성공 혹은 실패 여부를 몰라도 됨

finally 는 자동으로 다음 핸들러에 결과와 에러를 전달

 

 

참고자료

https://ko.javascript.info/async

애플리케이션의 설정은 배포시 상황 ( 프로덕션, 개발, 로컬)에 따라 달라질 수 있습니다.

이때 환경변수를 상황에 맞게 저장을 해두는 것이 좋습니다.

.env (.env.local, .env.dev, .env.prod) 라는 곳에 저장된 변수들을 불러오는 방법은 아래와 같습니다.

 

설치

$ npm i --save @nestjs/config

 

예시

//package.json
{
  ...
  "scripts": {
    ...
    "start:local": "NODE_ENV=local nest start --watch", 
    ...
  },
  ...
}
// app.module.ts

import * as dotenv from 'dotenv';
dotenv.config();
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';

@Module({
  imports: [
    ConfigModule.forRoot({
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('dev', 'prod', 'local', 'debug').default('local'),
      }),
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV}`,
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get<string>('DB_HOST'),
        port: configService.get<number>('DB_HOST'),
        username: configService.get<string>('DB_USERNAME'),
        password: configService.get<string>('DB_PASSWORD'),
        database: configService.get<string>('DB_NAME'),
        autoLoadEntities: true,
        synchronize: true,
        logging: true,
        namingStrategy: new SnakeNamingStrategy(),
      }),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
// main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config'; // import

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  // .env.local 에 있는 변수 호출
  await app.listen(configService.get<number>('PORT'), configService.get<string>('HOST_IP'));
  console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

 

터미널에서 실행 방법

$ npm run start:local

+ Recent posts