truthy falsy
falsy한 값은
unll, undefined, 0, ‘’, NaN 으로 총 5개이다.
5개를 제외한 나머지는 truthy이다.
const value = null;
일 때, !value를 하면 true가 출력된다.
삼항연산자
let a = 3;
a > 0 ? console.log(“양수다”) : console.log(“음수다”);
[조건 ? true일 때 실행 : false일 때 실행]
let score = 100;
score >= 90
? console.log("A+")
: score >= 60
? console.log("B+")
: console.log("F")
학점 계산 프로그램이다.
이런 식으로 중첩 삼항 연산자를 사용해도 되나,
가독성이 안좋기에 그냥 if문으로 쓰자
단락회로 평가
단락회로 평가는 왼쪽에서 오른쪽으로 연산하게 되는
논리연산자의 연산 순서를 이용하는 문법이다.
console.log(true && true);
and 연산자 같은 경우, 둘 다 true일 때만 true를 반환한다.
그런데 만약
console.log(false && true);
처럼 첫 번째 피연산자가 false가 되어버리면
굳이 뒤의 피연산자를 확인할 필요가 없어진다.
이런식으로 뒤의 위치한 피연산자를 확인할 필요 없이 연산을 끝내버리는 것을
단락회로 평가라고 함
console.log(true || false);
or 연산자는 둘 중 하나만 true여도 true를 반환한다.
그래서 첫 번째 피연산자가 true가 되어버리면
굳이 뒤의 피연산자를 확인할 필요가 없어진다.
or연산자도 단락회로평가가 이루어진다.
truthy falsy 일 때 단락회로 평가를 멋있게 이용할 수 있다.
const getName = (person) => {
if(!person){
return "객체가 아닙니다";
}
return person.name;
};
let person;
const name = getName(person);
console.log(name);
이 코드를 단락회로 평가를 이용해서 더 간단하게 만들 수 있다.
const getName = (person) => {
return person && person.name;
};
let person;
const name = getName(person);
console.log(name);
더 간단하게 만든 코드이다.
undefined가 반환된다.
if문으로 person.name에 접근하지말라고 한 것도 아닌데 정상적으로 처리가 됐다.
그 이유는 undefined가 falsy한 값이기 때문이다.
return person && person.name
은 and연산자로 써있고 둘 다 true여야 true를 반환한다.
하지만 person 변수는 undefined 하고 파라미터 또한 undefined 하다
즉 falsy하기 때문에 굳이 person.name을 확인할 필요없이 연산지 종료되는 것이다.
매개변수 person은 전달된 undefined를 그대로 반환한다.
const getName = (person) => {
const name = person && person.name;
return name || "객체가 아닙니다";
};
let person = {name : "이용진"};
const name = getName(person);
console.log(name);
이렇게 되면 person은 truthy한 값이기에
const name = person && person.name;
에서 person.name까지 훑게된다.
그리고 person.name인 “아용진”을 변수 name에 저장한다
return name || "객체가 아닙니다";
name에 저장된 “이용진”은 truthy한 값이기에
or 연산자 단락회로 평가로 인해 두 번째 피연산자는 확인하지않은 채
name에 들어간 “이용진”을 그대로 반환하면서 연산을 종료한다.
정리하면
A && B 연산자를 사용할 때 A가 Truthy한 값이라면 B가 결과값이 되고
A가 Falsy한 값이라면 결과는 A가 된다.
A || B 연산자를 사용할 때 A가 Truthy한 값이라면 A가 결과값이 되고
A가 Falsy한 값이라면 결과는 B가 된다.
조건문 업그레이드
function isKoreanFood(food){
if(food === '불고기' || food === "비빔밥" || food === "떡볶이"){
return true;
}
return false;
}
불고기, 비빔밥, 떡볶이가 있으면 true를 반환하는 함수이다.
문자열로 이뤄진 한식 중에
입력받은 파라미터가 존재하는지 안하는지 확인하려면
let isKoreanFood = (food) => {
if(['불고기','떡볶이','비빔밥'].includes(food)){
return true;
}
return false;
}
배열.includes(매개변수) 를 사용하면 된다.
이는 매개변수가 배열안에 존재하면 true 존재하지않으면 false를 반환한다.
여러가지 케이스 중 하나인지를 비교할때, 배열의 내장함수 includes를 사용하면 된다.
const getMeal = (mealType) => {
if(mealType === '한식') return "불고기";
if(mealType === '양식') return "파스타";
if(mealType === '중식') return "멘보샤";
if(mealType === '일식') return "초밥";
return "굶기";
};
console.log(getMeal("한식"));
console.log(getMeal("중식"));
console.log(getMeal(""));
식사 유형에 따라 각각의 음식을 반환하는
getMeal 함수를 만들었다.
지금처럼 적은 유형은 만들만 한데.
만약 식사 유형이 더 많이 늘어난다면 힘들 것이다.
이것은 객체의 프로퍼티에 접근하는 ‘괄호표기법’을 통해
혁신적으로 해결할 수 있다.
const meal = {
한식 : "불고기",
중식 : "멘보샤",
일식 : "초밥",
양식 : "스테이크",
인도식 : "카레"
};
const getMeal = (mealType) => {
return meal[mealType] || "굶기";
}
console.log(getMeal("한식"));
getMeal 함수를 호출할 때
mealType에 “한식”이라는 값을 전달했기 때문에
meal이라는 객체에서 “한식”이라는 key를 갖는 value를 가져오게 되는 것이다.
비 구조화 할당(구조 분해 할당)
let arr = ["one", "two", "three"];
let [one,two,three] = arr;
arr이라는 배열의 0번 째 인덱스는 one이라는 변수,
1번 째 인덱스는 two라는 변수,
2번 째 인덱스는 three라는 변수에 할당해라 라는 뜻이다.
대괄호를 이용해서 배열의 값을 순서대로 할당 받아서 사용할 수 있는 방법을 배열의 비구조화 할당이라고 표현함
‘배열의 기본 변수 비 구조화 할당’라고 하기도 한다.
let [one,two,three] = ["one", "two", "three"];
더 간단하게 위처럼 써도 된다.
오른쪽의 배열에서 0번, 1번, 2번으로 인덱스를 꺼내서 쓸 수도 있다.
배열의 선언 자체에서 분리한다고 해서 ‘배열의 선언 분리 비 구조화 할당’ 라고 한다.
명칭까지 익힐 필요는 없다. 다만 배열을 비 구조화해서 할당하는 여러가지 방법이 있다고만 알아두자.
let [one,two,three,four] = ["one", "two", "three"];
만약 오른쪽에 요소를 3개 가지고 있는데
4번째 변수까지 할당받고 싶으면 어떻게 될까?
마지막 four 변수는 할당되지 못하기 때문에
undefined라는 값을 가지게 된다.
단 변수에 undefined가 할당되면 안되는 상황도 존재한다
이럴 때를 대비해서 변수의 기본값을 설정하면 된다.
let [one,two,three,four = “four”] = ["one", "two", "three"];
위처럼 기본 값을 설정하면 값이 할당되지 않았을때
자동으로 기본 값(“four”)이 할당되게 된다.
let a = 10;
let b = 20;
[a,b] = [b,a];
배열의 비구조화 할당을 이용하면
두 개변수의 값을 서로 바꾸는 스왑에 활용할 수도 있다.
a와 b의 값을 스왑했다.
객체의 비구조화 할당
let object = {one : "one", two: "two", three: "three"};
let one = object.one;
let two = object.two;
let three = object.three;
value를 얻기 위해, 점 표기법을 통해 하나하나 변수에 넣어줬다.
let object = {one : "one", two: "two", three: "three"};
let {one, two, three} = object;
object에 key값을 기준으로 one이라는 key를 갖는 value를
변수 one에 저장하고,
two라는 key를 갖는 프로퍼티의 value를 변수 two에 저장,
three라는 key를 갖는 프로퍼티의 value를 변수 three에 저장한다.
객체의 비구조화 할당은 배열의 인덱스를 이용하는 것이 아니라, 즉 순서가 아니라 key값을 기준으로 할당한다.
let object = {one : "one", two: "two", three: "three", name: "이용진"};
let {name, one, two, three} = object;
console.log(one,two,three,name)
순서가 상관없기에 변수 name을 맨 앞에 넣어도
제대로 “이용진”이 출력된다.
객체 프로퍼티의 key값을 기준으로 접근을 하기에, 변수 명이 key값으로 강제되는 부분이 있었다.
이것을 극복하는 방법이 존재한다.
let object = {one : "one", two: "two", three: "three", name: "이용진"};
let {name, one, two, three} = object;
console.log(one,two,three,name)
만약 위 코드에서 name부분을 다른 변수 이름을 사용하고 싶다면
let object = {one : "one", two: "two", three: "three", name: "이용진"};
let {name: myName, one:oneOne, two, three} = object;
console.log(oneOne,two,three,myName)
위와 같이 변수의 이름을 바꿔서도 할당 받을 수 있다.
name이라는 키 값을 기준으로 value를 myName이라는 변수에 할당하겠다
one이라는 키값을 기준으로 value를 oneOne라는 변수에 할당하겠다.
let object = {one : "one", two: "two", three: "three", name: "이용진"};
let {name: myName, one:oneOne, two, three,abc=“four”} = object;
console.log(oneOne,two,three,myName,abc)
배열처럼 기본 값을 설정할 수 있다.
spread 연산자
const cookie = {
base : "cookie",
madeIn : "korea"
};
const chocochipCookie = {
base : "cookie",
madeIn : "korea",
toping: "chocochip"
};
const blueberryCookie = {
base: "cookie",
madeIn: "korea",
toping: "blueberry"
};
const strawberryCookie = {
base: "cookie",
madeIn: "korea",
toping: "strawberry"
};
base와 madeIn이라는 프로퍼티는 계속 겹친다.
모든 쿠키객체에 포함되고 계속 똑같은 값을 가진다
이렇게 중복된 프로퍼티를 계속 작성해야 할 상황에는
오늘 배울 spread연산자를 쓰면 된다.
const cookie = {
base : "cookie",
madeIn : "korea"
};
const chocochipCookie = {
...cookie,
toping: "chocochip"
};
const blueberryCookie = {
...cookie,
toping: "blueberry"
};
const strawberryCookie = {
...cookie,
toping: "strawberry"
};
다른 객체들에
…cookie 를 쓰니까
cookie 객체에 담긴 프로퍼티들을 다 포함한다
…으로 표현하는 것을 spread연산자 라고 한다.
펼치는 연산자이다. 객체의 값을 새로운 객체에 펼쳐주는 역할을 하는 연산자이다.
const noTopingCookies = ['촉촉한쿠키', '안촉촉한쿠키'];
const topingCookies = ["바나나쿠키", "블루베리쿠키","딸기쿠키","초코칩쿠키"];
const allCookies = [...noTopingCookies,...topingCookies];
console.log(allCookies);
두 배열을 합칠때 이전에 배운 concat 메서드를 사용해도 괜찮지만
위 처럼 스프레드 연산자를 통해 합칠 수도 있다.
객체의 프로퍼티를 펼치는 것 처럼 배열의 원소를 순서대로 펼칠 수 있다.
동기&비동기
이 방식들은 자바스크립트가 동작하는 방식과 매우 큰 연관이 있다.
function taskA(){
console.log("TASK A");
}
function taskB(){
console.log("TASK B");
}
function taskC(){
console.log("TASK C");
}
taskA();
taskB();
taskC();
위의 코드에서 자바스크립트가 3가지의 작업하는 방식은 대략
Thread -> taskA -> taskB -> taskC 순서이다
프로그래밍된 코드를 계산해서 실행시켜야하니까
연산과정이 필요할 것이다.
연산과정은 코드 한 줄 한 줄마다 다 시행된다.
이 때 그 연산과정을 수행하는 친구 우리의 일꾼을 스레드 라고 부른다.
다시 말해 스레드는 코드를 한 줄 한 줄 실행시켜주는 친구를 말함
Thread는 taskA작업 중에는 혼신을 다 쏟아붓고 있기 때문에
taskB를 같이 실행시키지않는다.
taskA가 끝나면 taskB는 그때야 비로소 수행이 될 수 있다.
지시한 순서대로 작업을 진행하면서
앞의 작업이 끝날 떄 까지 기다렸다가 바로 뒤의 작업이 실행되는 이러한
방식을 동기적 방식이라고 한다
thread에서 작업 하나가 수행되고 있을 때
다른 작업을 동시에 할 수 없는 방식을 블로킹 방식이라고도 부른다
지금은 내가 실행중이니 넘보지마~ 라고 블로킹한다고 생각하자
우리가 작성한 모든 코드들은 thread라는 녀석이
동기적 방식으로 처리시켜주고 있었던 것이다.
동기적으로 처리를 해도 하나하나의 작업들이 길어도 0.5초로 짧게 끝나고 있어서
지금으로서 문제 될 것은 없어보인다.
[자바스크립트는 코드가 작성된 순서대로 작업을 처리함. 이전 작업이 진행중일때는 다음 작업을 수행하지 않고 기다림. 먼저 작성된 코드를 먼저 다 실행하고 나서 뒤에 작성된 코드를 실행한다 -> 동기방식의 처리]
그런데 만약
Thread -> taskA -> taskB -> taskC
taskB를 처리하는데 20초가 걸려버리는 등 무거운 작업이 포함되어있다면 상황이 달라진다.
동기적 처리의 단점은 하나의 작업이 너무 오래 걸리게 될 시, 오래걸리는 하나의 작업이 종료되기 전까지 모든 작업이 올 스탑되기 때문에 전반적인 흐름이 느려진다.
웹사이트에서 버튼 하나하마다 30초씩 걸리면 속터짐
-> 동기처리 방식의 문제점
멀티쓰레드
Thread -> taskA
ThreadA -> taskB
ThreadB -> taskC
코드를 실행하는 일꾼 Thread를 여러 개 사용하는 방식인 ‘MultThread’ 방식으로 작동시키면 이런 식으로 작업 분할이 가능하다.
오래 걸리는 일이 있어도 다른 일꾼 Thread에게 지시하면 되므로 괜찮음
하!지!만!!!!!!!!!!
자바스크립트는 싱글 Thread로 동작한다. 즉 이런 방식으로 일꾼을 여러 개 사용하는 방법은 사용이 불가함!!!!!
taskC
taskB
Thread -> taskA
싱글 쓰레드 방식을 이용하면서, 동기적 작업의 단점을 극복하기 위해
여러 개의 작업을 동시에 실행 시킴
즉, 먼저 작성된 코드의 결과를 기다리지 않고 다음 코드를 바로 실행한다 -> 비동기 처리 방식
하나의 방식이 Thread를 점유하지 않는, 즉 Thread에게 블로킹을 하지않는 이런 방식을 ‘논 블로킹 방식’이라고 한다.
다 동시에 실행시켜버리면 이 작업들이 정상적으로 끝났는지는 어떻게 확인하고, 결과는 어떻게 확인 할 수 있을까
taskA((resultA) => {
console.log(`A 끝났습니다. 작업결과 : ${resultA}`);
});
taskB((resultB) => {
console.log(`B 끝났습니다. 작업결과 : ${resultB}`);
});
taskC((resultC) => {
console.log(`C 끝났습니다. 작업결과 : ${resultC}`);
});
비동기적으로 실행된 A,B,C 각각의 함수들에게 너 작업 끝나면 이거 실행해
라는 의미로 콜백함수를 붙여서 전달하면 된다.
function taskA(){
console.log("A 작업 끝");
};
taskA();
console.log("코드 끝");
taskA(); 가 끝나기 전까지
console.log(“코드 끝”)은 실행 될 수조차 없는
동기적 방식이다.
비동기 방식을 보기위해 setTimeout()함수를 사용한다.
setTimeout함수는 두 개의 매개변수를 가지는데, 첫 번째 매개변수는
콜백함수가 들어가고, 두 번째 매개변수는 시간이 들어간다. 1초는 1000이다.
function taskA(){
setTimeout(()=>{
console.log("A TASK END")
},2000)
};
taskA();
console.log("코드 끝");
실행시키면 taskA(); 의 실행이 끝나는 것을 기다리지않고
console.log(“코드 끝”)이 먼저 실행된다.
이 방식을 비동기 방식이라고 한다.
지시 순서는 taskA()가 우선이지만 기다리지않고
console.log가 먼저 수행이 됐다.
function taskA(a,b,cb){
setTimeout(()=>{
const res = a+b;
cb(res);
},3000)
};
taskA(3,4,(res)=>{
console.log("A TASK RESULT : ", res)
});
console.log("코드 끝");
비동기 방식 예제
자바스크립트 엔진은 어떻게 동기적 코드와 비동기적 코드를 구분해서 수행하는가?
자바스크립트 엔진은 Heap, Call Stack 두 가지의 구성요소로 이뤄져있다.
Heap은 변수나 상수들에 사용되는 메모리를 저장하는 영역
Call Stack은 우리가 작성한 코드의 실행에 따라서 호출 스택이라는 것을 쌓는 영역
function one(){
return 1;
}
function two(){
return one() + 1;
}
function three() {
return two() + 1;
}
console.log(three());
[Call Stack]
Main Context
자바스크립트 코드의 실행이 시작되면
Call Stack에 보이는 것 처럼 자바스크립트 코드들 가장
최상위 문맥인 Main context가 CallStack에 가장 먼저 들어오게 된다.
Main context가 CallStack에 들어오는 순간이 프로그램의 실행 순간인 것이고,
이 Main context가 CallStack에서 나가는 순간이 프로그램의 종료 순간인 것이다.
[Call Stack]
three()
Main Context
첫 번째 코드가 실행되고, three()라는 함수를 실행하게 되므로
three()함수가 CallStack에 추가되게 된다.
three()함수를 실행을 해야 결과값을 return받아서 console에 찍어서 쓸 수 있기 때문이다.
[Call Stack]
two()
three()
Main Context
three()함수에서는 two()함수를 실행하고 있다.
two()라는 함수를 호출했기때문에 얘도 실행시켜봐야한다.
CallStack에 three()를 추가한 것처럼 two()를 쌓게 된다.
[Call Stack]
one()
two()
three()
Main Context
two()를 실행하려고 들어가 봤더니 또 one()을 실행하고 있다.
Call Stack에는 또 one()함수가 추가된다.
[Call Stack]
out -> one()
two()
three()
Main Context
one() 함수를 실행하려 들어갔더니
1을 return하고 종료되는 것을 볼 수 있다
Call Stack에서는 종료된 함수는 바로바로 스택에서 빠진다.
Call Stack에 사용되는 Stack라는 구조는
마치 프링글스 통 처럼 가장 마지막에 들어온 것부터
가장 먼저 제거되는 구조이다.
가장 마지막에 호출된 힘수인 one함수가 가장 먼저 종료가 되고
가장 먼저 제거가 된다.
[Call Stack]
out -> two()
three()
Main Context
one()함수의 리턴값을 받아봤으니까
two()함수에서는 return 1+1을 해서 2를 return하면서
two()도 실행이 종료된다. 그래서 똑같이 Call Stack에서 제거 됨
[Call Stack]
out -> three()
Main Context
three()함수도 2+1을 return하면서 종료될 수가 있다.
그래서 three()함수도 Call Stack에서 제거가 된다.
[Call Stack]
Main Context
three()함수의 반환값은 3이므로
console.log(3)을 출력한다.
console.log까지 끝났으면 프로그램이 종료가 된 것이다.
이런 상황에서는 프로그램을 종료하기 위해서 Main context까지 Call Stack에서 빠지게 된다
자바스크립트 엔진은 이런방식으로 프로그램을 실행하고 종료하게 된다.
코드를 직접 실행시키는 친구를 thread라고 함
thread는 Call Stack 하나만을 담당하고 Call Stack의 작동방식대로 명령을 처리한다고 보면 되는데,
이 js엔진은 Call Stack이 단 하나 있기 떄문에 그래서 그래서 js가 싱글 thread로 작동한다고 생각하면 된다.
js가 비동기로 작동하는 코드를 어떻게 실행시키는가?
function asyncAdd(a,b,cb){
setTimeout(()=>{
const res = a+b;
cb(res);
},3000);
};
asyncAdd(1,3,(res) => {
console.log("결과 : ", res);
})
비동기 처리를 위해서는 js엔진 뿐만아니라
wehb APIs, Event Loop, Callback Queue 등 새로운 구성요소가 생겨났다
js 엔진 외의 새로운 구성요소들은
js엔진과 웹브라우저간의 상호작용 등등을 처리하기 위해 존재하는 것
대표적인 상호작용은 비동기처리이다.
프로그램이 실행되면
[Call Stack]
asyncAdd()
Main Context
asyncAdd함수가 가장먼저 실행된다.
[Call Stack] [Web APIs] [Callback Queue]
setTimeout() cd()
asyncAdd()
Main Context
asyncAdd 함수 안에서 setTimeout이라는 비동기 함수를 호출하고 있다.
[Call Stack] [Web APIs] [Callback Queue]
asyncAdd() setTimeout() cb()
Main Context
setTimeout 함수는 3초를 기다리게 되어있는데
이것을 Call Stack에서 그대로 실행하게 놔둔다면
3초를 기다렸다가 다음 코드를 실행해야할 것이다
그렇기에 js엔진은 비동기로 실행되는 setTimeout 함수를
web APIs로 넘겨버린다.
web APIs로 넘어간 setTimeout함수는 멈추는 것이 아니라
그냥 거기서 그대로 3초를 기다리게 된다.
[Call Stack] [Web APIs] [Callback Queue]
out ->asyncAdd() setTimeout() cb()
Main Context
setTimeout 함수가 Call Stack에 머무르지않게 되기 때문에
바로 다음 코드를 수행할 수 있게 되고,
asyncAdd함수를 끝낼 수 있게 된다. 그리고 Call Stack에서 제거 됨
[Call Stack] [Web APIs] [Callback Queue]
Main Context out -> setTimeout() cb()
asyncAdd()함수가 제거가 되고
setTimeout함수의 기다림이 끝난 상황에서는
setTimeout()함수는 제거가 되고,
Web APIs에 있는 cb()함수가 Callback Queue로 옮겨지게 된다.
왜냐면 이제 수행을 해야하기 때문이다
[Call Stack] [Web APIs] [Callback Queue]
cb() <——————————————————[Event Loop] ——————
Main Context
Event Loop에 의해서 다시 Call Stack로 옮겨질 수 있다.
어떤 콜백함수가 Web APIs에 있다가 Callback Queue로 이동을 해서 Call Stack로 들어간다는 의미는
콜백함수가 실제로 수행이 이루어진다라고 생각하면 된다.
Event Loop가 Callback Queue에서 Call Stack로 cb()함수를 넘겨줄텐데, 어떤 식으로 넘겨주게 되냐면
Event Loop는 Call Stack에 Main context를 제외한 다른 함수가 남아있지않는지 계속 확인한다.
만약 아무것도 남아있지않다면 그때는 cb()함수를 수행할 수 있게 되니까 cb를 넘겨서 콜백함수가 실행이 된다.
[Call Stack] [Web APIs] [Callback Queue]
out -> cb()
Main Context
결과 출력 이후에 모두 수행이 끝났기 때문에 Call Stack에서 제거되게 된다.
마지막에 남은 Main Context는 프로그램 실행이 완료됐기 때문에 제거되면서 비동기 작업을 포함한 js프로그램이 완료가 되게 된다.
js엔진이 비동기 코드를 어떻게 수행하는지 알고 있으면
동기코드와 비동기 코드를 같이 수행했을 때
이래서 이런 문제가 발생했고, 이렇게 바꾸면 되겠구나 라는걸 더 떠올리기 쉬워진다.
function taskA(a,b, cb){
setTimeout(() => {
const res = a+b;
cb(res);
},3000);
};
function taskB(a,cb){
setTimeout(() => {
const res = a * 2;
cb(res);
}, 1000);
}
function taskC(a,cb){
setTimeout(() => {
const res = a * -1;
cb(res);
}, 2000);
}
taskA(4,5,(a_res) => {
console.log("TASK A RESUTL : ", a_res);
taskB(a_res,(b_res)=>{
console.log("TASK B RESULT : ",b_res);
taskC(b_res,(c_res) => {
console.log("TASK C RESULT : ",c_res);
});
});
});
console.log("코드 끝");
비동기 처리의 값을 또 다른 비동기처리의 값으로 전달 할 수 있다.
이러한 상황이 생각보다 좀 많다.
근데 가독성이 아주 안좋다.
콜백 안에 콜백 안에 콜백 안에…
이러한 로직이 계속 길어지게 되는 현상을 callback 지옥이라고 한다.
이런 것을 구원하기 위한 비동기 담당 객체가 존재한다.
promise라고 한다.
콜백지옥에서 우리를 구원하다 Promise 객체
자바스크립트의 비동기를 돕는 객체
Promise를 사용하면 더이상 비동기 처리 함수에 콜백을 줄지어 전달할 필요가 없다
비동기 작업이 가질 수 있는 3가지 상태
- pending (대기 상태) : 비동기 작업이 진행중이거나 시작할수도 없는 뭔가 문제가 발생했음을 의미
- Fulfilled (성공) : 이행 또는 성공 상태, 비동기 작업이 우리가 의도한대로 정상적으로 완료
- Rejected(실패) : 비동기 작업이 모종의 이유로 실패(서버 응답 x, 시간이 너무 오래걸려서 자동 취소 등)
비동기 작업은 한번 성공하거나 실패하면 그걸로 작업이 끝남
Pending 상태에서 Fulfilled 상태로 변하는 것을 resolve(해결)가 이루어졌다고 함
Pending 상태에서 Rejected 상태로 변하는 것을 reject(거부)가 이루어졌다고 함
function isPositive(number,resolve,reject){
setTimeout(() => {
if (typeof number === 'number'){
// 성공 -> resolve
resolve( number > 0 ? "양수":"음수");
}else{
// 실패 -> reject
reject("주어진 값이 숫자형 값이 아님");
}
},2000);
}
isPositive('a', (res) => {
console.log("성공적으로 수행됨 : ",res);
}, (err) => {
console.log("실패 함 : ",err)
});
resolve 역할을 하는 콜백함수와
reject 역할을 하는 콜백함수가 정상적으로 수행됨
function isPositiveP(number){
const executor = (resolve,reject) => {
setTimeout(()=>{
if(typeof number === 'number'){
// 성공 -> resolve
console.log(number); // 확인코드
resolve(number >= 0 ? "양수" : "음수");
}else{
// 실패 -> reject
reject("주어진 값이 숫자형 값이 아닙니다.")
}
},2000);
};
const asyncTask = new Promise(executor);
return asyncTask;
};
isPositiveP(101);
isPositiveP 함수에서 executor 비동기 작업을 실행시켜야하는데,
실행 시키는 방법은 매우 간단하다.
const asyncTask = new Promise(executor);
이런식으로 작성을 하게되면 비동기 작업 자체인 Promise를
저장할 상수 asyncTask를 만든 다음에 new 키워드를 사용해서
Promise 객체를 생성하면서 프로미스 객체의 생성자로
비동기 작업의 실질적인 실행자인 executor 함수를 넘겨주게 되면
전달하는 순간 자동으로 executor함수가 바로 수행이 된다.
exector 함수는 비동기 작업을 실질적으로 수행하는 그런 함수다.
executor를 담은 Promise 객체를 asyncTask라는 상수에 담았으니까 상수를 그냥 return 해주면 된다.
그리고 isPositiveP 함수에 마우스를 올리면 반환값이 Promise로 바뀌어 있는 것을 확인 할 수 있다.
어떤 함수가 Promise를 반환한다는 것은 이 함수는 비동기 작업을 하고,
그 작업의 결과를 Promise 객체로 반환받아서 사용할 수 있는 함수라고 이해하면 된다.
function isPositiveP(number){
const executor = (resolve,reject) => {
setTimeout(()=>{
if(typeof number === 'number'){
// 성공 -> resolve
console.log(number); // 확인코드
resolve(number >= 0 ? "양수" : "음수");
}else{
// 실패 -> reject
reject("주어진 값이 숫자형 값이 아닙니다.")
}
},2000);
};
const asyncTask = new Promise(executor);
return asyncTask;
};
isPositiveP(101);
이제는 isPositive()함수를 호출을 하고 Promise 객체를 반환을 받을 것이다
Promise 객체를 한번 사용해보겠다.
반환되는 객체를 res라는 변수에 저장한다.
Promise객체의 비동기 처리의 결과를 사용하는 방법은 매우매우 간단간단함
res.then(콜백함수).catch(콜백함수); 를 사용하면 된다.
function isPositiveP(number){
const executor = (resolve,reject) => {
setTimeout(()=>{
if(typeof number === 'number'){
// 성공 -> resolve
console.log(number); // 확인코드
resolve(number >= 0 ? "양수" : "음수");
}else{
// 실패 -> reject
reject("주어진 값이 숫자형 값이 아닙니다.")
}
},2000);
};
const asyncTask = new Promise(executor);
return asyncTask;
}
const res = isPositiveP([]); //
res.then((res)=>{console.log("작업 성공 : ", res)}).catch((err)=>{console.log("작업 실패 : ",err)});
Promise 객체의 메서드인 then과 catch를 사용하게 되면
resolve를 사용했을 때 전달한 결과값을 then메서드에서 받아올 수 있다.
reject를 수행했을 때는 catch 메서드에서 받아올 수 있다.
콜백 지옥에서 Promise를 사용하는 방법을 알아보자
function taskA(a,b,cb){
setTimeout(()=>{
const res = a+b;
cb(res);
},3000)
}
function taskB(a,cb){
setTimeout(()=>{
const res = a*2;
cb(res);
},1000)
}
function taskC(a,cb){
setTimeout(()=>{
const res = a * -1;
cb(res);
},2000)
}
taskA(3,4,(a_res) => {
console.log("task A : ", a_res);
taskB(a_res, (b_res) => {
console.log("task B : ",b_res);
taskC(b_res,(c_res)=>{
console.log("task C : ",c_res);
})
});
})
존재하는 비동기 작업들인 taskA,taskB,taskC를 다 Promise를 사용해서
비동기 처리를 하는 함수로 바꿔보겠다.
+a
function taskA(a,b,cb){
return new Promise((resolve,reject) => {
setTimeout(()=>{
const res = a+b;
cb(res);
},3000)
});
}
function taskA(a,b,cb){
const executorA = (resolve, reject) => {
setTimeout(()=> {
const res = a + b;
cb(res);
},3000);
};
return new Promise(executorA);
}
두 개의 함수는 같다
Promise의 인자로 들어가는 xecutor 함수를 화살표 함수 처리해서 헷갈려 보이는 것일 뿐임
resolve,reject를 쓸 것이기에, cb는 다 지워준다
function taskA(a,b){
return new Promise((resolve,reject) => {
setTimeout(()=>{
const res = a+b;
resolve(res);
},3000)
});
}
function taskB(a){
return new Promise((resolve,reject) => {
setTimeout(()=>{
const res = a*2;
resolve(res);
},1000)
});
}
function taskC(a){
return new Promise((resolve,reject)=> {
setTimeout(()=>{
const res = a * -1;
resolve(res);
},2000)
});
}
Promise 객체를 반환 한다는 것은
그 함수는 비동기적으로 동작을 하고, 반환한 이 Promise 객체를 이용해서
비동기 처리의 결과값을 then과 catch로 이용할 수 있게 만들겠다라는 의미이다.
taskA(3,4,(a_res) => {
console.log("task A : ", a_res);
taskB(a_res, (b_res) => {
console.log("task B : ",b_res);
taskC(b_res,(c_res)=>{
console.log("task C : ",c_res);
})
});
})
이제는 이 콜백 헬을 Promise를 통해 바꾸면 어떻게 바뀔지 알아보자
taskA(5,1).then((a_res)=>{
console.log("A RESULT :", a_res);
taskB(a_res).then((b_res)=>{
console.log("B RESULT :", b_res);
taskC(b_res).then((c_res)=>{
console.log("C RESUTL :", c_res);
})
})
})
Promise 를 사용해서 콜백 헬이 그대로 발생했음을 알 수 있다.
Promise 객체와 그 메서드인 then을 이런식으로 사용하는 것이 아니라서 그렇다
다시 바꿔서 사용해보겠다.
taskA(5,1).then((a_res)=>{
console.log("A RESULT :", a_res);
return taskB(a_res); // 여기까지는 B의 Promise를 받은 것이고
}).then((b_res)=>{
console.log("B RESULT :", b_res);
return taskC(b_res); // 여기까지는 C의 Promise를 받은 것이다.
}).then((c_res) => {
console.log("C RESULT :", c_res);
});
이렇게 then 메서드를 계속 이어서 붙이는 것을
then 체이닝 이라고 부른다.
taskA의 결과값인 a_res를 인자로 전달한 taskB에 또 then메서드를 이용하고,
그 결과값인 b_res를 taskC의 인자로 전달하고, 또 then메서드를 붙여서 마지막까지 호출을 하는 방식
const bPromiseResult = taskA(5,1).then((a_res)=>{
console.log("A RESULT :", a_res);
return taskB(a_res);
});
console.log("흐름이")
console.log("끊어져도")
console.log("다시")
console.log("이어갈 수 ")
console.log("있어요")
bPromiseResult.then((b_res)=>{
console.log("B RESULT :", b_res);
return taskC(b_res);
}).then((c_res) => {
console.log("C RESULT :", c_res);
});
콜백 헬과 다르게 중간에 끊고 다른작업을 중간에 넣어도 된다.
Promise객체를 이용하면 비동기처리를 호출하는 코드(taskA)와
결과를 처리하는 코드(bPromiseResult)를 분리해 줄 수가 있어서
콜백헬을 피하고, 더 가독성있고 깔끔한 비동기처리를 할 수 있도록 도와준다.
async는 비동기를 다루는 기능이자 Promise를 더 쉽게 이용할 수 있도록
도와주는 친구이다.
async를 붙이고 툴팁을 열어보면 Promise를 반환하는 것을 알 수 있다.
function hello(){
return 'hello';
}
async function helloAsync(){
return 'hello Async';
}
console.log(hello());
console.log(helloAsync());
hello()의 결과값은 hello로 잘 나왔는데,
helloAsync의 결과값은 Promise {<pending>}으로 나왔다.
helloAsync 함수는 Promise를 return하고 있다고 확인할 수 있었다.
helloAsync의 결과값은 Promise가 되는 것이다.
async를 함수에 붙이게 되면 함수는 자동적으로 Promise를 return하는 비동기 처리 함수가 된다 라고 생각할 수 있다.
helloAsync 함수가 Promise를 반환하고 있다는 뜻은.
호출할 필요 없이 then을 쓸 수 있다는 소리도 된다.
function hello(){
return 'hello';
}
async function helloAsync(){
return 'hello Async';
}
helloAsync().then((res)=>{console.log(res);})
hello Async라는 문자열이 res에 전달 됐고, console.log를 통해 출력이 된다.
hello Async는 어디서 왔을까?
async라는 키워드를 붙여준 함수의 return 값은
비동기 작업 객체 Promise의 resolve의 결과값이 된다.
async를 붙이고 return만 해도 Promise를 return을 하면서,
resolve를 “hello Async” 값으로 수행한 것과 똑같은 효과를 얻는다.
그렇기에 hello Async의 값이 res에 들어올 수 있게 되는 것이다.
async는 함수에 옵션을 붙이듯 붙여서
함수가 Promise를 반환하도록 만드는 능력이 있다.
function delay(ms){
return new Promise((resolve) => {
setTimeout(()=>{
resolve();
},ms);
});
}
async function helloAsync(){
return delay(3000).then(()=>{
return "hello Async";
});
}
helloAsync().then((res)=>{console.log(res);})
우리가 원하는 동작은 3초 기다렸다가 “hello Async” 반환하는 것인데,
코드가 너무 거창하게 긴 것 같다.
이럴 때 await 를 사용하는 것이다.
await이라는 키워드를 비동기 함수의 호출 앞에 붙이게 되면
이 비동기 함수가 마치 동기적인 함수처럼 작동하게 된다.
await 키워드가 붙은 함수의 호출은 함수가 끝나기 전까지
그 아래에 있는 코드를 수행하지 않는다.
즉 await 줄은 다 동기적으로 수행되게 된다.
그리고 async 키워드가 붙은 함수 내에서만 사용할 수 있다.
function delay(ms){
return new Promise((resolve) => {
setTimeout(()=>{
resolve();
},ms);
});
}
async function helloAsync(){
await delay(3000);
return "hello async";
}
async function main(){
const res = await helloAsync();
console.log(res);
}
main()
async와 await를 통해서 비동기 코드, promise를 반환하는 함수를
동기적인 함수를 호출하는 것처럼 사용할 수 있도록 만들어주는 것까지 배움
API
API(Application Programming Inertface)
응용 프로그램 프로그래밍 인터페이스
클라이언트 ——[데이터 요청] ——> 서버 ——[데이터 검색] ——> 데이터베이스 ——[데이터 찾기]——> 서버 ——[요청 데이터 전달]——> 클라이언트
브라우저가 서버에 데이터를 요청, 서버는 데이터베이스에서 데이터를 검색
데이터 베이스에서 찾은 데이터를 서버에게 보내주고, 서버가 브라우저에게 응답
우리는 데이터 요청, 요청 데이터 전달 그리고 서버 정도만 알면 된다,
서버에게 데이터를 요청하고, 전달 받는 과정을 API 호출 이라고 부른다.
API라는 녀석은 쉽게 말해서 클라이언트와 서버의 연결 다리이자 대화를 할 수 있는 방법
정리 : API를 호출한다는 것은 다른 프로그램에게 데이터를 받기 위해 말을 거는 것.
API 호출하는법
https://jsonplaceholder.typicode.com/
개발자들을 위해 무료로 더미데이터를 응답해주는 서비스
let response = fetch("https://jsonplaceholder.typicode.com/posts").then((res)=>{
console.log(res)
});
//Promise를 반환하는 함수는 비동기처리를 하는 함수이고,
//이 함수의 처리결과는 then을 통해 사용할 수 있다.
fetch(“주소”)
fetch 내장함수는 자바스크립트에서 api호출을 할 수 있도록 돕는다.
fetch를 통해 api를 호출하게 되면
그 api의 결과값을 그냥 반환하는 것이 아니라, api 성공 객체 자체를 반환하기 때문에
이런식으로 Response라는 객체가 출력되는 것을 볼 수 있다.
정리하면 우리가 우체통에서 꺼낸 것이
편지안의 내용을 꺼낸 것이 아니라
편지의 봉투를 꺼낸 것이다. 즉 포장지를 꺼낸 것이다.
포장지에서 json 형식의 값을 뜯어오자
async function getData (){
let rawResponse = await fetch("https://jsonplaceholder.typicode.com/posts");
let jsonResponse = await rawResponse.json();
console.log(jsonResponse);
};
getData();
데이터가 정상적으로 잘 들어오는 것을 확인할 수 있다.
'프로그래밍 언어 > JavaScript' 카테고리의 다른 글
JSON.stringify(), JSON.parse(), toJSON() 정리 (0) | 2023.09.26 |
---|---|
Array 객체의 여러 함수들 (0) | 2023.09.26 |
자바스크립트 기초 훑기 - 1 (0) | 2023.07.19 |
자바스크립트 함수,이벤트 (0) | 2023.06.28 |
자바스크립트 제어문 (0) | 2023.06.26 |