익명의 개발노트

Async/await/ promise 본문

프로그래밍/javascript

Async/await/ promise

캡틴.JS 2019. 8. 31. 16:32
반응형

1. promise란??

사전적의미는 약속이다.

오브젝트 안에 오브젝트를 포함하는 자바스크립트 오브젝트의 특별한 형태이다.

프로미스에 접근할 수 있는방법은 .then()을 통해서 접근할 수 있다.

function getFirstUser() {
    return getUsers().then(function(users) {
        return users[0].name;
    });
}

프로미스가 미래 시점의 데이터를 위한 것이고, 프로미스를 가지고 있기만 하면, 그 데이터가 미래시점인지 현재인지는 상관없다.

why?? then을 통해서 부르기만 하면 되는 것임.

프로미스를 사용한다는 것은 비동기함수가 될꺼야, 라는 것을 의미한다. 그래서 리턴값을 지금 사용하든지, 아니든지,

나는 비동기함수야의 의미가 된다.

프로미스 특징은 비동기 작업을 순서대로 진행 할 수있게 해준다.

실행하는 방법은 크게 3가지있다.

1)  const ex = new Promise((resolve, reject) =>{})
2)  const ex = Promise.resolve(param);
3)  const ex = Promise.reject('error');

1)번은 비동기 작업이 성공했을때, resolve를 호출하고, 실패했을때 reject를 호출하면 된다.

2)번은 성공했을때의 값, 3번은 실패했을때의 값을 얻을 수있다.

프로미스는 then을 통해서 순서대로 처리된다. 아래 참조

Promise.resolve(10).then(data => {
	console.log('then1',data);
	Promise.resolve(20);
}).then(data => {
	console.log('then2', data);
}).then(data => {
	console.log('then3', data);
}).then(data => {
	console.log('then4', data);
});    

실패했을때, catch를 통해서 reject부분이 실행하게 하면 된다.

Promise.reject(10).then(data => {
	console.log('then1 :',data);
	return 20;
}).catch(error => {
	console.log('catch :',error);
	return 30;
}).then(data => {
	console.log('then2 :',data);
});

위의 콘솔에 찍히는 내용을 보면

'catch' : 10, 'then2': 30 이 찍힌다.

애초에 reject에 10을 넣었으면, reject는 catch부분에서 실행된다. 

첫번째 then은 실행되지 않는다. 바로 catch로 실행이 되기때문에 then1은 찍히지가 않는다. 그리고 순서대로 실행되기 때문에

catch의 리턴값이 30이기 때문에 마지막 프로미스에서 콘솔에 찍히는 값은 30이 된다.

리턴값을 넣지 않을 경우 undefinded가 될 것이다.

Promise.resolve(10).then(data => {
	console.log('then1 :',data);
	return 20;
}).catch(error => {
	console.log('catch :',error);
	return 30;
}).then(data => {
	console.log('then2 :',data);
});

 위 코드를 보면 이번에는 resolve에 값을 넣은 경우다. 이런경우 값을 보면, 

'then1':10, 'then2': 20이 된다. resolve값은 catch에서 잡을 수 없기때문에, catch를 건너뛰고 실행이 된다.

4) finally 

finally는 프로미스가 이행 또는 거부 된 상태일 때 호출되는 메서드이다. finally는 이전에 사용된 프로미스를 그대로 반환한다.

따라서, 처리됨 상태인 프로미스 데이터를 건드리지 않고 추가 작업을 할때 사용한다.

Promise.resolve(10).then(data => {
	console.log('then1 :',data);
	return 20;
}).catch(error => {
	console.log('catch :',error);
	return 30;
}).then(data => {
	console.log('then2 :',data);
}).finally(data => {
	console.log('finally:',data);
});

위의 내용에 따르면, 마지막 finally의 값은 undefinded이다.  새로운 프로미스를 생성하지 않는다. 따라서 then2에서 프로미스 생성은 끝났다.

5) 프로미스 병렬처리 : Promise.all은 여러개의 프로미스를 병렬로 처리할 때 사용한다.

지금까지는 순서대로 실행되는 프로미스였고, 병렬로 처리되는 코드는 아래와 같다.

requestData1().then(data => console.log(data));
requestData2().then(data => console.log(data));

위의 형태를 아래와 같이 변경하면,

Promise.all([requestData1(), requestData2()]).then(([data1,data2]) => {
	console.log(data1, data2);
}

Promise.all은 입력된 모든 프로미스가 fulfilled 상태가 되어야 처리됨 상태로 된다.  하나라도 거부되면, rejected로 반환한다.

2. async/await

위 코드를 보면 프로미스이면 then을 쓰기까지 기다리면 된다.

따라서, 위 코드를 아래와 같이 바꿀 수 있다.

async function getFirstUser(){
	let users = await getUsers();
    return users[0].name;
}

async는 비동기임을 알려준다. 이 함수가 비동기함수라고 명시.

await은 프로미스의 값이 사용가능 할때까지 메소드의 일을 일시중지 시킨다. 

에러처리할때는 아래와 같이 변경가능하다.

async function getFirstUser(){
    try{
    	let users = await getUsers();
        return users[0].name;
    } catch (err) {
    	return {
        	name : 'defualt user'
        }
    }
}

그럼 왜 프로미스를 알아야 하나??

1) 기다리지(await) 않는 상황

만약 그냥 호출한다면??

let users = getFirstUser();

위 값은 미동기 함수로 만들어졌기 때문에 그냥 실행시, getFirstUser의 값이 아닌 프로미스 객체를 가리킬 것이다.

하지만 await을 통해서 내가 프로미스 객체를 사용하겠다, 비동기 객체를 사용하겠다 명시해주면 올바른 값이 출력된다.

비동기함수는 저절로 기다리지 않는다. 코딩하는 사람이 반드시 await을 지정해주어야 한다.

물론 프로미스 객체로 받아온다면, 메모이즈 같은 것들을 컨트롤 할 수 있다.

2) 다수의 값을 await 하는 상황

await은 한번에 한개만 기다리게 할 수 있다.

만약에 두개이상의 값을 await을 통해 받아오려면??

let [foo, bar] = await Promise.all([getFloor(), getBar()]);

 위와 같이 비 구조화 할당을 통해서 가져올 수있다.

3. 스택전체가 비동기가 되어야 하는 상황

어딘가에 await를 쓰기 시작했다면, 전체 스택에 영향을 미치는 문제가 생긴다.

하나의 비동기함수를 사용하기 위해서는 이상적으로 caller(함수를 부르는 곳)는  자기자신이 비동기함수이어야 한다.

이렇게 되면, 함수를 부를때 콜스택이 쌓이는데, 이 부분에 대해 전체적으로 연쇄적인 효과가 있다.

그리고 그 효과때문에 콜백에서 async/await으로 점진적으로 바꾸기 힘들다.

 (1) 콜백을 받는 비동기 함수의 결과를 프로미스로 취급하여 해결하는 방법

function getFirstUser(callback) {
    return getUsers().then(function(users) {
        return callback(null, users[0].name);
    }).catch(function(err) {
        return callback(err);
    });
}

프로미스 라이브러리를 사용하여 아래와 같이 변경

function getFirstUser(callback) {
    return getUsers().then(function(users) {
        return users[0].name;
    }).nodeify(callback);
}

(2) 비동기 함수가 있고, 그 안에 콜백 함수를 불러야 할 경우

function callbackToPromise(method, ...args) {
    return new Promise(function(resolve, reject) {
        return method(...args, function(err, result) {
            return err ? reject(err) : resolve(result);
        });
    });
}

위의 내용을 아래와 같이 변경한다. 우리가 일반적으로 사용하는 형태

async function getFirstUser() {
    let users = await callbackToPromise(getUsers);
    return users[0].name;
}

4. 에러 핸들링은 잊으면 안된다.

프로미스의 오랜문제이면서, async/await의 문제이기도 하다.

에러핸들링을 하지않을 경우 다른 부분에서 로직이 끊길 수도 있다.

myApp.endpoint('GET', '/api/firstUser', async function(req, res) {
    let firstUser = await getFirstUser();
    res.json(firstUser)
});

myApp.endpoint가 프로미스나 비동기가 아니라면, 내가 보낸 핸들러 함수에서 리턴 받는 것에 await이나 catch()를 걸지 않는다면, 에러는 처리되지 않을 것이다.

위 코드를 아래와 같이 프로미스로 바꿔보면,

myApp.endpoint('GET', '/api/firstUser', function(req, res) {
    return getFirstUser().then(function(firstUser) {
        res.json(firstUser)
    });
});

getFirstUser에 대한 콜백을 전해줬지만, 에러 처리에 대한 것은 아무것도 전달해주지 않았다.

getFirstUser이 함수가 비동기이기 때문에 에러를 발생시켜도 위에서 자동으로 잡지 못한다.

따라서, 코드 상위 레벨에서 에러처리를 해주어야 한다.

myApp.registerEndpoint('GET', '/api/firstUser', async function(req, res) {
    try {
        let firstUser = await getFirstUser();
        res.json(firstUser)
    } catch (err) {
        console.error(err);
        res.status(500);
    }
});

 

반응형

'프로그래밍 > javascript' 카테고리의 다른 글

Destructuring  (0) 2020.01.09
Generator  (0) 2019.10.06
ES6 특징 핵심요약  (0) 2019.05.01
Prototype, function Method and class  (0) 2019.04.11
Event handler with loop in javascript  (0) 2019.04.11
Comments