ES6+ 템플릿 리터럴로 동적인 문자열 만들기



#1 Template literals(템플릿 리터럴)을 사용한 코드



var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

템플릿 리터럴은 이중 따옴표나 작은 따옴표 대신 백틱(Grave accent)를 사용합니다.

플레이스 홀더 $와 중괄호( ${ expression } )를 사용해 표현식과 그 사이의 텍스트를 함께 전달합니다.

엔터키를 이용해 개행처리까지 가능합니다.



#2 Nesting templates(템플릿 중첩) 코드


예를들어, header태그의 class를 정할때 삼항연산자를 통해 class에 넣을 텍스트를 그대로 출력할 수 있습니다.


먼저 ES5 에서는


var classes = 'header'
classes += (isLargeScreen() ?
'' : item.isCollapsed ?
' icon-expander' : ' icon-collapser');



두번째로 ES2015에서 중첩(nesting)없이 템플릿 리터럴을 사용한 경우


const classes = `header ${ isLargeScreen() ? '' :
(item.isCollapsed ? 'icon-expander' : 'icon-collapser') }`;

${ expression }을 사용해 script를 사용함.


ES2015에서 nested 템플릿 리터럴을 사용한 경우


const classes = `header ${ isLargeScreen() ? '' :
`icon-${item.isCollapsed ? 'expander' : 'collapser'}` }`;

코드 가독성이 많이 높아 진다.



#3 Tagged templates(태그된 템플릿) 코드


tagged template(태그된 템플릿)는 template literals(템플릿 리터럴)의 더욱 발전된 형태입니다.

tagged template(태그된 템플릿)는 함수로 정의된다.

사용방법은 아래와 같이 함수명에 template literals(템플릿 리터럴)을 붙여서 사용한다


// 함수 정의
function taggedFn(strings, ...expressions) { // 1번
return "Something.";
}

const val1 = 12;
const val2 = 34;
const result = taggedFn`first-${val1}-second-${val2}`; // 2번
console.log(result); // 'Something.'

1번에서 taggedFn이라는 tagged template(태그된 템플릿) 함수를 정의한다.

2번에서 taggedFn 함수를 호출한다. 여기서 함수명과 template literals(템플릿 리터럴)을 붙여서 사용하는 점이 우리가 알고 있던 문법과 다르기 때문에 어색할 수 있다.

여기서 template literals(템플릿 리터럴) 표현식( ${ } )을 기준으로 일반 stringsexpressions를 파싱하여 배열로 만들어 매개변수로 넣어 준다.

strings : ['first-', '-second-', '']

expressions : [12, 34]


아래의 여러 가지 예제를 통해 매개변수 strings, expressions 값이 어떻게 파싱되는지 알아보자.


const val1 = 12;
const val2 = 34;

taggedFn`first-${val1}-second-${val2}-third`;    // 1번
// string : ['first-', '-second-', '-third' ];
// expressions : [12, 34] ;

taggedFn`first-${val1}-second-${val2}`;         // 2번
// string : ['first-', '-second-', '' ];
// expressions : [12, 34] ;

taggedFn`${val1}-second-${val2}`;                // 3번
// string : ['', '-second-', '' ];
// expressions : [12, 34] ;

// 함수 정의
function taggedFn(strings, ...expressions) {
console.log(strings.length === expressions.length + 1); // true
}

2번, 3번의 strings 배열값을 자세히 보면 template literals(템플릿 리터럴) 표현식으로 시작거나 또는 끝나면 '' 빈 문자열이 들어가는 것을 확인할 수 있다.

1번, 2번, 3번의 expressions값을 동일하다.

이 결과로 strings 배열의 개수는 expressions 배열의 개수보다 항상 하나 더 많다는 점을 확일할 수 있다.


tagged template(태그된 템플릿)을 활용해 expressions로 전달된 문자열을 HTML strong 태그로 감싸 강조를 하는 함수를 만들어 보자.


function setStrongify(strings, ...expressions) {
return strings.reduce(
(preValue, str, i) => expressions.length === i
? `${preValue}${str}`
: `${preValue}${str}<strong>${expressions[i]}</strong>`
, '');
}

const val1 = 12;
const val2 = 34;
const result = setStrongify`first ${val1} second ${val2}`;
console.log(result);
// first <strong>12</strong> second <strong>34</strong>


tagged template(태그된 템플릿) 함수의 반환값이 꼭 문자열이 아니어도 된다.

참고로 react에서 사용하는 스타일 적용 패키지 styled-components는 tagged template(태그된 템플릿) 함수가 리액트 컴포넌트를 반환한다.



블로그 이미지

steadily

,

ES6+ 향상된 비동기 프로그래밍(3) - async await


 -. async await는 비동기 프로그래밍을 동기 프로그래밍처럼 작성할 수 있도록 함수에 추가된 기능이다.

 -. Promise가 javascript의 표준이 되고 2년 후(ES2017)에 async await도 표준이 되었다.

-. Promise가 async await 보다 큰 개념이며, async await를 사용해 then 메서드 chain 보다 가독성을 높여줄 수 있다.



1.  async await 이해하기



#1 async await 함수는 프로미스를 반환한다.


Promise = 객체

async await = 함수에 적용되는 개념


Promise를 반환하는 async await 함수


async function getData() {
return 1234; // 1번
}
getData().then(data => console.log(data)); // 1234

async await 함수 내부에서 반환하는 값이 Promise라면 그 객체를  그대로 반환한다.


예외 발생 시에는 rejected(거부됨) 상태인 Promise가 반환된다.


async function getData() {
throw new Error('1234');
}
getData().catch(error => console.log(error));
// 에러 발생: 1234



#2 await 키워드를 사용하는 방법


await 키워드는  async 함수 내부에서만 사용할 수 있다.

만약 await 키워드 오른쪽에 Promise를 입력하면 그 Promise가 settled(처리됨) 상태가 될 때까지 기다린다.

따라서 await 키워드를 사용하면 비동기 처리를 기다리면서 순차적으로 코드를 작성할 수 있다.


await 키워드 사용 예


function requestData(value) {
return new Promise(resolve => {
setTimeout(() => {
console.log('requestData: ', value);
resolve(value);
}, 1000);
});
}
async function getData() {
const data1 = await requestData(1);   // 1번
const data2 = await requestData(2);   // 2번
console.log(data1, data2);            // 3번
return [data1, data2];
}
getData();      
// requestData: 1
// requestData: 2
// 1 2

1번, 2번의 함수 requestData가 반환하는 Promise가 settled(처리됨) 상태가 될 때까지

3번의 코드는 실행 되지 않는다.



#3 async awaitPromise 보다 가독성이 좋다



async awaitPromise 코드 비교


function getDataPromise() { // 1번
asyncFn1()
.then(data => {
console.log(data);
return asyncFn2();
})
.then(data => {
console.log(data);
})
}
async function getDataAsync() { // 2번
const data1 = await asyncFn1();
console.log(data1);
const data2 = await asyncFn2();
console.log(data2);
}

1번은 Promise로 작성한 함수

2번의 async await 함수는 then 메서드를 호출할 필요가 없기 때문에 가독성이 좋아진다.


비동기 함수 간의 의존성이 높아질수록 async await의 가독성이 좋아지는 것을 볼 수 있다.

아래에서 asyncFn1, asyncFn2, asyncFn3 이 세 함수는 각각의 반환값을 다른 함수의 인수로 넣는 의존성을 가지고 있디.


function getDataPromise(){ // 1번
return asyncFn1()
.then(data1 => Promise.all([data1, asyncFn2(data1)]))
.then(([data1, data2]) => {
return asyncFn3(data1, data2);
});
}
async function getDataAsync() { // 2번
const data1 = await asyncFn1();
const data2 = await asyncFn2(data1);
return asyncFn3(data1, data2);
}

1번에서는 asyncFn3 함수에 인수를 전달하기 위해 Promise.all을 사용했다.

2번에서 async await 함수는 복잡한 의존성을 직관적이게 표현했다.



2.  async await 활용하기


#1 비동기 함수를 병렬로 실행하기


async await  함수에서 순차적으로 실행하는 코드


async function getData() {
const data1 = await asyncFn1();
const data2 = await asyncFn2();
//...
}

위의 두 함수 사이에 의존성이 없다면 동시에 실행하는 것이 좋을 것이다.

Promise는 생성과 동시에 비동기 코드가 실행된다.

그래서 아래와 같이 두개의 Promise를 생성하고 await 키워드를 나중에 사용하여 병렬 코드를 만들어 보자.


async function getData() {
const promise1 = asyncFn1();
const promise2 = asyncFn2();
const data1 = await promise1;
const data2 = await promise2;
//...
}

두개의 프로미스가 생성되고 각자의 비동기 코드가 실행된다.

이 두 프로미스가 생성된 후 await 키워드를 사용해 기다리기 때문에 두 개의 비동기 함수가 병렬로 처리된다.


아래와 같이 Promise.all을 사용하면 더 간결한 코드를 작성할 수 있다.


async function getData() {
const [data1, data2] = await Promise.all(asyncFn1(), asyncFn2());
//...
}




#2 예외 처리


async await 함수 내부에서 발생하는 예외는 아래와 같이 try catch 문으로 처리할 수 있다.


async function getData(){
try {
await getAsync(); // 1번
return getSync(); // 2번
} catch (error) {
console.log(error);
}
}

1번(비동기 함수)과 2번(동기 함수)에서 발생하는 모든 예외가 catch 문에서 처리 된다.

여기서 getData 함수가 async await 함수가 아니였다면 1번의 getAsync 함수에서 발생하는 예외는 catch 문에서 처리 되지 않는다. 이유는 getAsync 함수의 처리가 끝나는 시점을 알 수 없기 때문이다.



#2 Thenable


asycn await는 ES6의 Promise가 아니더라도 then 메서드를 가진 객체를 Promise 취급한다.

이렇게 Promise가 아닌데 then 메서드를 가진 객체를 Thenable이라고 부른다.


async await 함수에서 Thenable을 사용한 예


class ThenableSample {
then(resolve, reject) { // 1번
setTimeout(() => resolve(1234), 1000);
}
}
async function asyncFn() {
const result = await new ThenableSample(); // 2번
console.log(result); // 1234
}

1번 : then 메서드를 가지고 있어 new ThenableSample() 객체는 Thenable이다.

2번 : async await 함수는 ThenablePromise 처럼 처리한다.


블로그 이미지

steadily

,

ES6+ 향상된 비동기 프로그래밍(2) - 프로미스 활용


ES6에서 Promise가 javascript 언어에 포함이 되었습니다.

- 이제 콜백 지옥에서 벗어나 보자. 


이전글 [React] - ES6+ 향상된 비동기 프로그래밍(1) - 프로미스




1.  프로미스 활용하기


#1 Promise(프로미스) 병렬처리: Promise.all


then 메서드로  프로미스  chain을 만들어 순차적으로 실행하기.


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


병렬로 실행 되는 코드


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


Promise.all 사용

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


Promise.all 함수는 프로미스를 반환한다.

입력된 모든 프로미스가 settled(처리됨) 상태가 되어야 마찬가지로 settled(처리됨) 상태가 된다.

만약 하나라도 rejected(거부됨) 상태가 된다면 Promise.all 함수가 반환하는 프로미스도 rejected(거부됨) 상태가 된다.



#2 가장 빨리 처리된 프로미스 가져오기: Promise.race


말 그대로 여러 프로미스 중 가장 빨리 settled(처리됨) 상태가 된 프로미스를 반환하는 함수다.


Promise.race를 사용한 간단한 예제


Promise.race([
requestData(),
new Promise((_, reject) => setTimeout(reject, 3000)),
])
.then(data => console.log(data))
.catch(error => console.log(error));

requestData 함수가 3초 안에 데이터를 받으면 then

그렇지 않으면 catch 메서드가 호출된다.



#3 프로미스를 이용한 데이터 캐싱


데이터를 가져오는 작업에 실패하는 경우가 고려되지 않은 간단한 예제


let cachedPromise;
function getData() {
cachedPromise = cachedPromise || requestData(); // 1
return cachedPromise;
}
getData().then(v => console.log(v));
getData().then(v => console.log(v));

1번에서 처음 호출할 때만 requestData 함수가 호출된다.



2.  프로미스 사용 시 주의사항


#1 return 키워드 깜빡하지 않기




Promise.resolve(10)
.then(data => {
console.log(data);
Promise.resolve(20); // need return
})
.then(data => {
console.log(data) // undefined
});

then 메서드 내부 함수에서 return 키워드를 입력하는 것을 깜빡하기 쉽다.



#2 Promise는 불변 객체


프로미스가 수정된다고 생각하고 작성한 코드


function requestData() {
const p = Promise.resolve(10);
p.then(() => { // 기존 객체 수정 X
return 20; // 새로운 Promise 반환
});
return p;
}

requestData().then(v => {
console.log(v) // 10
});

이제 원하는 값 20이 나오게 하려면 아래아 같이 수정해야 한다.


function requestData() {
return Promise.resolve(10).then(v => {
return 20;
});
}

requestData().then(v => {
console.log(v) // 20
});



#3 Promise를 중첩해서 사용하지 않기


콜백 패턴 만큼 복잡해지므로 지양해야 하는 중첩 코드


requestData1().then(data1 => {
requestData2(data1).then(data2 => {
//...
});
});


아래와 같이 사용하는 것이 좋다.


requestData1()
.then(data1 => {
return requestData2(data1);
})
.then(data2 => {
//...                    // 1번
});


1번 에서 data1 변수를 참조해야 한다면? 

Promise.all 함수를 사용해 중첩하지 않고 해결할 수 있다.


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



#4 동기(sync) 코드 예외 처리



동기 코드에서 발행한 예외가 처리되지 않는 코드


function requestData() {
doSync(); // 동기 코드
return fetch()
.then(data => console.log(data))
.catch(error => console.log(error));
}


fetch 전에 doSync 함수가 반드시 호출되어야 하는 게 아니라면  아래와 같이 then 메서드 안쪽으로 넣어 주는 게 좋다.


function requestData() {
return fetch()
.then(data => {
doSync(); // 동기 코드
console.log(data);
})
.catch(error => console.log(error));
}

이제 catch에서는 doSync 함수에서 발생하는 예외 처리가 된다.

블로그 이미지

steadily

,

ES6+ 향상된 비동기 프로그래밍(1) - 프로미스


ES6에서 Promise가 javascript 언어에 포함이 되었습니다.

- 이제 콜백 지옥에서 벗어나 보자. 


1.  프로미스에 대해 알아보자.


#1 Promise(프로미스) 간단 사용법



아래의 코드는 Promise를 사용해 순차적으로 비동기 프로그래밍을 작성한것이다.


getData()
.then(data => {
console.log(data);
return getData2();
})
.then(data => {
console.log(data);
//.....
});

콜백 함수를 찾아 다닐 필요 없이 순차적으로 작성할수 있어 직관적이다.


프로미스의 세 가지 상태


- pending(대기중) : 결과를 기다리는 중

- fulfilled(이행됨) : 수행이 정상적로 끝났고 결괏값을 가지고 있음

- rejected(거부됨) : 수행이 비정상적으로 끝났음


 fulfilled(이행됨), rejected(거부됨) 상태를 settled(처리됨)  상태라고 한다.

프로미스는 settled(처리됨) 상태가 되면 더 이상 다른 상태로 변경되지 않는다.

pending(대기중) 상태일 때만  fulfilled(이행됨) 또는 rejected(거부됨) 상태로 변할 수 있다.


프로미스는를 생성 하는 세 가지 방법


const promise1 = new Promise((resolve, reject) => {
//...
// resolve(data);
// or reject('error message');
});
const promise2 = Promise.reject('error message');
const promise3 = Promise.resolve(param);

promise1new 키워드를 사용해서 프로미스를 생성한다.

이때 생성된 프로미스는 대기 중 상태가 된다.

생성자의 매개변수로 resolve와 reject라는 콜백함수를 입력할 수 있다.

비동기로 어떤 작업을 수행하고 성공했을 때 resolve를 호출하고 promise1 객체는 fulfilled(이행됨)

실패 시 reject가 호출되고 promise1 객체는 rejected(거부됨) 상태가 된다.

new 키워드를 사용해 Promise를 생성할 때 입력 된 함수가 만약 API를 요청하는 비동기 코드이면 생성되는 순간 요청을 보낸다.

생성 시 입력된 함수에 예외(exception)가 발생하면 rejected(거부됨)  상태가 된다.

promise2 : new 키워드를 사용하지 않고 Promise.reject를 호출하면 rejected(거부됨) 상태의 프로미스가 생성된다.

promise3 : Promise.resolve를 호출해도 프로미스가 생성된다. 이때 입력값이 프로미스였다면 그 객체가 그대로 반환되고, 프로미스가 아니면 fulfilled(이행됨) 상태인 프로미스가 반환된다.


다음은 Promise.resolve의 인수가 프로미스인 경우와 아닌 경우의 반환값을 보여준다.


const promise1 = Promise.resolve(1);
console.log(promise1 !== 1); // true
const promise2 = new Promise(resolve => setTimeout(() => {
resolve(10);
}, 1));
console.log(Promise.resolve(promise2) === promise2) // true

promise1 : 프로미스가 아닌 인수와 함께 Promise.resolve 함수를 호출하면 그 값 그대로 fulfilled(이행됨) 상태인 프로미스가 반환된다. promise1은 1을 데이터로 가진 프로미스다.

promise2 : Promise.resolve 함수에 프로미스가 입력되면 그 자신이 반환된다.




#2 Promise(프로미스) 이용하기 1편 : then


settled(처리됨) 상태가 된 프로미스를 처리할 때 then 메서드를 사용한다.

프로미스가 settled(처리됨) 상태가 되면 then 메서드의 인수로 전달된 함수가 호출된다.

다음은 then 메서드의 사용법이다.


requestApi().then(onResolve, onReject); // 1번
Promise.resolve(123).then(data => console.log(data)); // 123
Promise.reject('error').then(null, err => console.log(err)); // 에러 발생

1번 : 프로미스가 settled(처리됨) 상태가 되면 onResolve 함수가 호출되고, rejected(거부됨) 상태가 되면 onReject 함수가 호출된다.



then 메서드는 항상 프로미스를 반환한다. 따라서 하나의 프로미스로부터 연속적으로 then 메서드를 호출할 수 있다.


requestApi1()
.then(data => {
console.log(data);
return requestApi2(); // 1번
})
.then(data => {
return data + 1; // 2번
})
.then(data => {
throw new Error('something is wrong.');// 3번
})
.then(null, error => {
console.log(error);
});

1번 :  onResolve 또는 onReject 함수에서 프로미스를 반환하면 then 메서드는 그 값을 그대로 반환한다.

2번 : 만약 프로미스가 아닌 값을 반환하면 then 메서드는 fulfilled(이행됨) 상태인 프로미스를 반환한다.

3번 : onResolve 또는 onReject 함수 내부에서 예외가 발생하면 then 메서드는 rejected(거부됨) 상태인 프로미스를 반환한다.

결과적으로  then 메서드는 항상 프로미스를 반환한다.


프로미스가 rejected(거부됨) 상태인 경우에는 onReject 함수가 존재하는 then을 만날때까지 이동한다.


Promise.reject('err')
.then(() => console.log('then 1')) // 1
.then(() => console.log('then 1')) // 번
.then(() => console.log('then 3'), () => console.log('then 4')) // 2번
.then(() => console.log('then 5'), () => console.log('then 6'));// 3번

결과

then 4

then 5

Promise {<fulfilled>: undefined}

rejected(거부됨) 상태인 프로미스는 처음으로 만나는 onReject 함수를 호출하므로 1번은 생략

2번 : then 4 출력하는 onReject 함수는 undefined를 결과로 가지면서 fulfilled(이행됨) 상태인 프로미스를 생성한다.

3번 : 따라서 then 5가 출력 된다.

then 메서드의 가장 중요한 특징은 항상 연결된 순서대로 호출된다는 점이다. 이 특징 덕에 프로미스로 비동기 프로그래밍을 할 때 동기 프로그래밍 방식으로 코드를 작성할 수 있다.




#2 Promise(프로미스) 이용하기 2편 : catch


catch : 예외를 처리하는 메서드 ( then 메서드의 onReject 함수와 같은 역할 )


같은 기능을 하는 then 메서드와 catch 메서드


Promise.reject(1).then(null, error => {
console.log(error);
});
Promise.reject(1).catch(error => {
console.log(error);
});

예외 처리는 then 메서드의 onReject 함수보다는 catch  메서드를 이용하는 게 가독성 면에서 더 좋다.


then + onReject 문제점


Promise.resolve().then(
() => {
throw new Error('Something is wrong!');
},
error => {
console.log(error);
}
);

then 메서드의 onResolve 함수에서 발생한 예외는 같은 then 메서드의 onReject 함수에서 처리되지 않는다.

Chrome console에서는 Uncaught (in promise) Error: Something is wrong!  라는 에러가 발생한다. 

rejected(거부됨) 상태의 프로미스를 처리되지 않았기 때문이다.

다음과 같이 수정하면 이 문제는 해결된다.


Promise.resolve()
.then(() => {
throw new Error('Something is wrong!');
})
.catch(error => {
console.log(error);
});

이 처럼 catch를 사용해 직관적인 코드를 작성하자.


catch 메서드도 then 메서드 처럼 프로미스를 반환하기 때문에 프로미스 chain을 만들수 있다.


Promise.reject(1)
.then(data => {
console.log('then1: ', data);
return 2;
})
.catch(error => {
console.log('catch: ', error);
return 3;
})
.then(data => {
console.log('then2: ', data);
});
// catch: 2
// then2: 3



#2 Promise(프로미스) 이용하기 3편 : finally


finally는 프로미스가 fulfilled(이행됨) 또는 rejected(거부됨) 상태일 때 호출되는 메서드다.

2018년에 javascript 표준으로 채택됐다.

finally 메서드는 다음과 같이 프로미스 체인의 가장 마지막에 사용된다.


requestData()
.then(data => {
//..
})
.catch(error => {
//..
})
.finally(() => {
//..
});

finally 메서드는 .thenonFinally, onFinally )  코드와 유사하지만, 이전에 사용된 프로미스를 그대로 반환한다는 점이 다르다.

따라서 settled(처리됨) 상태인 프로미스의 데이터를 건드리지 않고 추가 작업을 할 때 유용하다.

아래는 데이터 요청의 성공, 실패 여부와 상관없이 서버에 로그를 보낼 때 finally메서드를 사용한 코드다.


function requestData(){
return fetch()
.catch(error => {
//..
})
.finally(() => {
sendLog('log');
});
}
requestData().then(data => console.log(data)); // 1번

1번의 datafinally 메서드가 호출 되기 이전의 프로미스다.

requestData 함수를 사용하는 입장에서는 finally 메서드의 존재 여부를 신경 쓰지 않아도 된다.



다음글 [React] - ES6+ 향상된 비동기 프로그래밍(2) - 프로미스 활용


블로그 이미지

steadily

,