??를 사용하면 짧은 문법으로 여러 피연산자 중 그 값이 ‘확정되어있는’ 변수를 찾을 수 있다.
a ?? b의 평가 결과는 다음과 같습니다.
a가null도 아니고undefined도 아니면a
그 외의 경우는b
다음 예시를 통해 살펴보자.
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// null이나 undefined가 아닌 첫 번째 피연산자
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
2. '||' 와 '??'의 차이
null 병합 연산자는 OR 연산자||와 상당히 유사하다.
실제로 위 예시에서??를||로 바꿔도 그 결과는 동일하다.
그런데 두 연산자 사이에는 중요한 차이점이 있다.
||는 첫 번째truthy값을 반환한다.
??는 첫 번째정의된(defined)값을 반환한다.
null과undefined, 숫자0을 구분 지어 다뤄야 할 때 이 차이점은 매우 중요한 역할을 한다.
자바스크립트에선 다음과 같이 변수에 함수를 할당할 수 있다. 이와같이 변수에 함수가 할당된 것을 함수 표현식이라고 한다.
그렇다면 왜 자바스크립트에선 다른 언어와 달리 함수 표현식으로 나타내는 것이 가능할까?
그것은 바로 자바스크립트가 함수를 값으로 취급하기 때문이다.
즉, 함수를 다른 변수와 동일하게 다룰 수 있다는 의미이다. 이와 같은 언어를일급 함수를 가졌다고 표현한다.
일급 함수를 가진 언어에서는 함수를 다른 함수에 매개변수로 제공하거나, 함수가 함수를 반환할 수 있으며, 변수에도 할당할 수 있다.
2. 콜백 함수
앞서 자바스크립트에서는 다른 함수의 매개 변수로 함수를 제공할 수 있다고 했다.
아래 예시를 살펴보자.
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "동의하셨습니다." );
}
function showCancel() {
alert( "취소 버튼을 누르셨습니다." );
}
// 사용법: 함수 showOk와 showCancel가 ask 함수의 인수로 전달됨
ask("동의하십니까?", showOk, showCancel);
ask함수의 인수로 showOk, showCancel 이라는 함수가 전달됐고 confirm(question)의 결과에 따라 해당 함수를 실행함을 알 수 있다.
이처럼 어떤 함수가 다른 함수의 인수로 전달되어 특정 동작후에 나중에 호출되는 함수를 콜백 함수라 한다.
너가 특정 동작을 한 후에 나를 호출해줘라는 의미에서 call back이라 부르는 것이다.
3. 함수 표현식 vs 함수 선언문
첫 번째 차이는 기본적인 문법의 차이이다.
function sum(a, b) {
return a + b;
} //함수 선언문
let sum = function(a, b) {
return a + b;
}; //함수 표현식
두 번째 차이는 자바스크립트 엔진이언제함수를 생성하는지에 있다.
함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성한다.
따라서 실행 흐름이 함수에 도달했을 때부터 해당 함수를 사용할 수 있다.
하지만 함수 선언문은 조금 다르다.
함수 선언문은 함수 선언문이 정의되기 전에도 호출할 수 있다.
이것이 가능한 이유는 자바스크립트 엔진이 변수 선언, 함수 선언문을 스크립트의 젤 위로 먼저 위치시키기 때문이다.
이것을 Hoisting이라고 한다.
따라서 스크립트는 함수 선언문을 먼저 처리한 이후에서야 나머지 코드를 실행한다.
따라서 스크립트 어디서든 함수 선언문으로 선언한 함수에 접근할 수 있는 것이다.
4. Arrow function(화살표 함수)
화살표 함수는 문법의 생김새에 따라 이름이 지어졌다.
굳이 구분할 것도 없지만 처음에 나도 화살표 함수를 접할 때 굉장히 익숙하지 않아서 적응하는데 애를 먹었다.
화살표 함수는 크게 세 가지 형태만 기억하면 된다.
4 - 1) 인수가 하나밖에 없다면 인수를 감싸는 괄호를 생략할 수 있다. 괄호를 생략하면 코드 길이를 더 줄일 수 있다.
let double = n => n * 2;
// let double = function(n) { return n * 2 }과 동일하다.
alert( double(3) ); // 6
4 - 2) 인수가 하나도 없을 땐 괄호를 비워놓으면 된다. 다만, 이 때 괄호는 생략할 수 없다.
let sayHi = () => alert("안녕하세요!");
sayHi();
4 - 3) 평가해야 할 표현식이나 구문이 여러 개인 함수가 있을 수도 있다.
이때는 중괄호 안에 평가해야 할 코드를 넣어주어야 한다.
그리고return 지시자를 사용해 명시적으로 결괏값을 반환해 주어야 한다.
let sum = (a, b) => { // 중괄호는 본문 여러 줄로 구성되어 있음을 알려줍니다.
let result = a + b;
return result; // 중괄호를 사용했다면, return 지시자로 결괏값을 반환해주어야 합니다.
};
alert( sum(1, 2) ); // 3
가장 먼저 해야 할 일은 편집할 이미지를 가져오는 것이다. getImage 함수가 이를 처리할 수 있다.
이미지가 성공적으로 로드된 후에만 해당 값을 resizeImage 함수에 전달할 수 있다.
이미지 크기가 성공적으로 조정되면 applyFilter 함수에서 이미지에 필터를 적용하려고 할 것이다.
이미지를 압축하고 필터를 추가한 후 이미지를 저장하고 모든 것이 올바르게 작동했음을 사용자에게 알리고 싶다!
결국 우리는 다음과 같은 코드를 얻게 될 것이다.
Nested Callback
위 코드를 보면 콜백 함수에 의존하는 많은 중첩 콜백 함수로 코드를 작성했다.
이것은 코드를 읽기 어렵게 만드는 수많은 중첩된 콜백 함수로 끝나기 때문에 종종 콜백 지옥이라고 한다!
다행히도 이제 우리를 도울 수 있는 Promise이라는 것이 생겼다!
Promise이 무엇인지, 이러한 상황에서 어떻게 우리를 도울 수 있는지 살펴보자!
2. Promise Syntax
프로미스는 생성자에 의해 생성된 프로미스 객체는 두 가지의 프로퍼티를 가진다.
바로 state, value 프로퍼티이다.
2 - 1) Promise State : 프로미스는 크게 세 가지 상태를 가진다.
Pending : 약속이 이행되지도 않았고 그렇다고 거부된 것도 아닌 상태
Fullfiled : 약속이 이행된 상태
Rejected : 에러가 있거나 어떤 문제로 인해 약속이 거부된 상태
let promise = new Promise((resolve, reject) => {
//new Promise에 전달되는 함수는 executor(실행 함수) 라고 부른다.
//executor의 인수 resolve와 reject는 자바스크립트가 자체적으로 제공하는 콜백함수다.
//개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 된다.
//다만, resolve나 reject 중 하나는 반드시 호출해야 한다.
//state는 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected"로 변한다.
//executor는 보통 시간이 걸리는 일을 수행한다 즉 비동기적으로 수행되는 일을 의미.
//일이 끝나면 resolve나 reject 함수를 호출하는데, 이때 프라미스 객체의 상태가 변화합니다.
//이행(resolved)되거나 거부(rejected)된 상태의 프라미스는 ‘처리된(settled)’ 프라미스라고 부릅니다.
//반대되는 프라미스로 '대기(pending)'상태의 프라미스가 있습니다.
resolve('promise')
})
2 - 2) Promise Value : 프로미스의 상태에 따라 Value 값이 달라짐.
즉, promise의 value는 resolve 메서드나 reject 메서드에 인수로 전달하는 값이 된다.
위 예시에서는 'promise'가 value가 될 것이다.
2 - 3) 소비자 메서드
마지막으로, 우리는 전체 promise 객체를 다루지 않고 promise에 의해 해결된 value만을 활용하고 싶을 것이다.
그때 아래 세 가지 메서드를 활용할 수 있다.
. then : resolve 메서드에 의해 받은 value를 인자로 받는다.
. catch : reject 메서드에 의해 받은 value를 인자로 받는다.
. finally : resolve, reject 메서드에 의해 받은 value를 인자로 받는다
.then의 첫 번째 인수는 프라미스가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받는다.
.then의 두 번째 인수는 프라미스가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받는다.
.then
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 함수는 .then의 첫 번째 함수(인수)를 실행합니다.
promise.then(
result => alert(result), // 1초 후 "done!"을 출력
error => alert(error) // 실행되지 않음
);
//작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 됩니다.
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1초 뒤 "done!" 출력
. catch
//에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)같이
//null을 첫 번째 인수로 전달하면 됩니다. .catch(errorHandlingFunction)를 써도 되는데,
//.catch는 .then에 null을 전달하는 것과 동일하게 작동합니다.
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력
//.catch(f)는 문법이 간결하다는 점만 빼고 .then(null,f)과 완벽하게 같습니다.
3. Promise Chaining
콜백 지옥 예제에서는 콜백을 실행하기 위해 여러 개의 콜백을 중첩해야 했다.
다행히. then 핸들러가 도움을 줄 수 있다!. then 자체의 결과는 약속 값이다.
이것은 우리가 원하는 만큼 많은. thens를 연결할 수 있음을 의미한다.
이전 then 콜백의 결과가 다음 then 콜백에 대한 인수로 전달되기 때문이다!
Promise chaining
어떤가? 콜백 지옥으로 가독성이 떨어진 코드보다 Promise Syntax로 인해 코드가 훨씬 명료해졌음을 알 수 있다.
4. Async/Await
Async/Await 문법은 JavaScript에서 비동기 동작을 추가하고 promise 작업을 더 쉽게 만드는 새로운 방법이다!
async 및 await 키워드를 도입하여 암시 적으로 promise를 반환하는 비동기 함수를 만들 수 있다.
이전에는 new Promise (() => {}), Promise 객체를 사용하여 명시 적으로 promise를 만들 수 있음을 확인했다.
Promise 객체를 명시 적으로 사용하는 대신 이제 암시적으로 객체를 반환하는 비동기 함수를 만들 수 있다!
이것은 우리가 더 이상 Promise 객체를 직접 작성할 필요가 없음을 의미한다.
비동기 함수가 암시적으로 promise를 반환한다는 사실은 꽤 훌륭하지만 await 키워드를 사용하면 비동기 함수의 진정한 힘을 볼 수 있다! await 키워드를 사용하면 awaited 값이 해결된 promise를 반환할 때까지 기다리는 동안 비동기 함수를 일시 중단할 수 있다.
이전에 then () 콜백에서 했던 것처럼 이 해결된 promise의 값을 얻으려면 대기된 promise 값에 변수를 할당할 수 있다!
4 - 1) async 함수
function 앞에async를 붙이면 해당 함수는 항상 프라미스를 반환한다.
프라미스가 아닌 값을 반환하더라도 이행 상태의 프라미스(resolved promise)로 값을 감싸 이행된 프라미스가 반환되도록 한다.
async function f() {
return 1;
}
f().then(alert); // 1
//명시적으로 프라미스를 반환하는 것도 가능한데, 결과는 동일합니다.
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
async가 붙은 함수는 반드시 프라미스를 반환하고, 프라미스가 아닌 것은 프라미스로 감싸 반환한다.
그런데async가 제공하는 기능은 이뿐만이 아니다.
또 다른 키워드await는async함수 안에서만 동작합니다.
await는 아주 멋진 녀석이다.
자바스크립트는await키워드를 만나면 프라미스가 처리(settled)될 때까지 기다린다.
결과는 그 이후 반환된다. 1초 후 이행되는 프라미스를 예시로 사용하여await가 어떻게 동작하는지 살펴보자.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
alert(result); // "완료!"
}
f();
함수를 호출하고, 함수 본문이 실행되는 도중에(*)로 표시한 줄에서 실행이 잠시 '중단’되었다가 프라미스가 처리되면 실행이 재개된다.
이때 프라미스 객체의result값이 변수 result에 할당된다. 따라서 위 예시를 실행하면 1초 뒤에 '완료!'가 출력된다.
await는 말 그대로 프라미스가 처리될 때까지 함수 실행을 기다리게 만든다. 프라미스가 처리되면 그 결과와 함께 실행이 재개된다.
프라미스가 처리되길 기다리는 동안엔 엔진이 다른 일을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.
await는promise.then보다 좀 더 세련되게 프라미스의result값을 얻을 수 있도록 해주는 문법이다.
promise.then보다 가독성 좋고 쓰기도 쉽다.
* 주의사항 : async함수가 아닌데await을 사용하면 문법 에러가 발생한다.
async/await를 함께 사용하면 읽고, 쓰기 쉬운 비동기 코드를 작성할 수 있다.
async/await를 사용하면promise.then/catch가 거의 필요 없다.
하지만 가끔 가장 바깥 스코프에서 비동기 처리가 필요할 때같이promise.then/catch를 써야만 하는 경우가 생기기 때문에async/await가 프라미스를 기반으로 한다는 사실을 알고 있어야 한다.
즉 async/await syntax는 promise syntax의 syntatic sugar라고 할 수 있다!