동기
-코드 실행 순서로 call stack에 실행 함수가 쌓인다(push)
-마지막에 쌓인 것 부터 함수가 실행된다.(LIFO)
-실행이 된 함수는 call satack에서 제거(pop)된다.
비동기
-Call Stack에서 비동기 함수가 호출되면 Call Stack에 먼저 쌓였다가 Web API(혹은 백그라운드라고도 한다)로 이동한 후 해당 함수가 등록되고 Call Stack에서 사라진다.
-Web API(백그라운드)에서 비동기 함수의 이벤트가 발생하면, 해당 콜백 함수는 Callback Queue에 push(이동) 된다.
-이제 Call Stack이 비어있는지 이벤트 루프(Event Loop)가 확인을 하는데 만약 비어있으면, Call Stack에 Callback Queue에 있는 콜백 함수를 넘겨준다.(push)
-Call Stack에 들어온 함수는 실행이 되고 실행이 끝나면 Call Stack에서 사라진다.
-다른 함수의 파라미터로 들어가 쓰이는 함수.
-에러와 결과를 같이 전달하는 것이 표준, 보통 첫 인자로 에러를 둚. (getUsers((err, users)=>{...});
//비동기 함수 : 2초 뒤에 Elice라는 이름을 인자로 받은 콜백함수의 인자로 넘겨준다.
function getName(cb) {
setTimeout(() => {
cb("Elice");
}, 2000);
}
//앞선 함수를 실행하려면 다음과 같이 getName 함수에 콜백함수를 넣어서 사용
getName((name) => {
console.log(name);
})
-개념상 파라미터로 받는 시간(최소 대기 시간, 무조건 지켜지진 않음)만큼 대기 후 작업을 수행하나 컴퓨터 성능이나 setTimeout 뒤의 코드에 따라 실제로 늦게 불려질 수 있다.
function next() {
console.log('do something');
setTimeout(next2, 1000);
}
function next2() {
console.log('and do another thing');
setTimeout(next3, 2000);
}
function next3() {
console.log('and then finally');
}
setTimeout(next, 1500);
"do something"
"and do another thing"
"an.d then finally"
console.log('hello');
setTimeout(() => {console.log('finished'); }, 0);
console.log('ok');
"hello"
"ok"
"finished"
hello 출력 - cb 함수 web api로 넘어가 계산 - ok 출력 - fininshed 출력
-각기 다른 콜백 방식들을 표준화, resolve와 reject를 써서 인터페이스를 하나로 합치기 위한 수단
-비동기 처리의 순서를 표현할 수 있다(비동기 작업을 표현하는 Js 객체)
-for/if문으로는 처리가 안되는 작업에 promise를 쓴다.
-매개변수 resolve, reject가 존재. Promise에 의해 결정되는 값을 받아 then으로 넘김
-비동기 작업의 진행, 성공, 실패 상태를 표현.
promise api는 비동기 api중 하나
-Job queue(or microtask queue, task queue보다 우선 순위가 높다)를 사용
https://joshua1988.github.io/web-development/javascript/promise-for-beginners/
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
new Promise(function (resolve, reject) {
//무언가 하고 성공시 resolve, 실패시 reject
//성공하면 콜백함수 resolve() 실행, 괄호 안의 내용 호출
const payload = {message:'done!'};
resolve(payload);
//실패하면 콜백함수 reject() 실행
const error = new Error('failed!');
reject(error);
});
resolve나 reject 중 하나를 넣고 then으로 처리를 받아서 해결.
const task = new Promise(function (resolve, reject) {
resolve('hi'); //성공시 이것
// reject(new Error('failed!')); 실패시 이것
});
task.then(
function handleSuccess(data) {
console.log(data);
},
function handleError(error) {
console.log(error);
}
);
//then은 promise된 콜백함수 사용에 필수, 성공/실패를 위한 값을 각각 받음.
//값을 받아서 다음을 처리해달라는 뜻.
// or
task.then(function handleSuccess(data) {
console.log(data);
}).catch(function handleError(error) {
console.log(error);
});
// 쉽게 쓰기
task.then(data => { console.log(data); })
.catch(error => { console.log(error); });
//reject는 catch로 연결
// .finallay( ()=> {console.log('hi');}); finally 쓸 수 있음
//promise를 에러처리할 떄도 쓸 수 있다
callback이 여러개 쓰이는데 이것을 그냥 then으로 값을 바로 받아서 씀.
//참고용, 화살표 함수를 활용한 함수 함축
const wait = (ms) => {
return new Promise((resolve, reject) => {
setTimeout( ()=> {
resolve()
}, ms)
});
};
//위 코드는 아래와 같음.
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function doSomething(num) {
return new Promise(resolve => {
console.log('do something');
resolve(num + 1);
});
}
function doAnother(num) {
return new Promise(resolve => {
console.log('do another');
resolve(num + 10);
});
}
doSomething(100)
.then(doAnother)
.then(num => {
console.log('num', num);
});
//then 을 통해 값을 받아 넘기면서 chanining 가능
//do something
//do another
// num 111
-Promise 생성자는 특정 작업을 Promise화 시켜주는 것일 뿐 그 이상 그 이하도 아니다.
setTimeout과 같은 cb만 지원하는 메소드를 promise화할 때 정도만 사용.
let promise = new Promise((resolve, reject)=>{
if(Math.random() <0.5) {
return reject('실패')
}
resolve(10)
})
-랜덤 함수를 사용 그 값이 0.5 밑이면 reject 호출, "실패" 반환
-값이 0.5 밑이 아니라면 resolve를 호출, 10을 반환
new Promise (callback;executor)
-cb 함수는 (resolve, reject) 두 콜백함수를 인자로 받아 각각 성공, 실패시 호출
-내부에서 직접 호출을 함으로써 성공/실패를 조작 가능.
★new Promise가 생성되면 executor는 바로 실행되므로 불필요한 소요가 발생할 수 있음!
//실 사용 예시
//생성
//findUserByUsername, findAddressByUserId 함수는 모두 Promise를 리턴
//findUserByUsername API 함수를 이용하여, value 값을 이용해 유저를 검색
export const findUserByUsername = (username) =>
new Promise((resolve, reject) => {
setTimeout(() => {
const foundUser = userData.find((user) => user.username === username);
if (foundUser) {
return resolve(foundUser);
}
reject(new Error("유저를 찾지 못했습니다."));
}, 300);
});
//findAddressByUserId API 함수를 이용하여, 찾은 user의 id를 이용해 주소를 검색
export const findAddressByUserId = (userId) =>
new Promise((resolve, reject) => {
setTimeout(() => {
const foundAddress = addressData.find(
(address) => address.userId === userId
);
if (foundAddress) {
return resolve(foundAddress);
}
reject(new Error("주소를 찾지 못했습니다."));
}, 300);
});
//사용
//searchAddress는 address 객체를 반환하는 Promise를 리턴
//catch를 이용해, 에러 발생 시 error에 error.message를 저장
function searchAddress() {
return findUserByUsername(value)
.then(user => findAddressByUserId(user.id))
.catch(e => {
error = e.message
})
}
db.getU
function getUsersPromise(params) {
return new Promise( (resolve, reject) =>{
getUsers(params, (err, users) =>{
if (err) {
reject(err);
return;
}
resolve(users);
});
});
}
//getUsers의 실행이 완료되면 err, users를 반환.
//콜백함수의 결과 에러는 reject로 users는 resolve로 전달됨.
// API콜 캐싱 예시
const cache = new Map();
function getSomethingFromRemote(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // fetch가 promise를 리턴하기 때문에 여기서도 강제로 promise화를 해줘야한다. 함수는 항상 통일된 리턴 타입을 가져야한다. 가령, 하나의 리턴문에서 Promise를 리턴한다면 다른 리턴문들도 Promise를 러턴해야한다.
}
if (url !== undefined && url !== null && url.length > 0) {
return fetch(url)
.then((response) => response.json())
.then((result) => {
cache.set(url, result);
return result;
}); // promise를 리턴
}
return Promise.reject(new Error("URL이 있어야합니다."));
}
// promise flattening 예시
fetch("https://ipapi.co/json")
// 아래의 response.json은 promise를 리턴하지만 promise가 resolve되면 다음 then으로 넘어간다.
.then((response) => response.json())
// 아래의 json.city는 string값(promise X)
// then에게 넘겨주는 함수는 primitive 또는 promise를 리턴할 수 있다. 결과는 같다.
// .then((json) => Promise.resolve(json.city))
.then((json) => json.city)
.then((city) => console.log(city));
-then 메서드에 성공시 실행할 콜백 함수를 인자로 넘긴다
-finally 메서드는 성공/실패 여부에 상관 없이 실행할 콜백 함수를 인자로 넘긴다.(settled 시 항상 호출)
1. Producer
const promise = new Promise( (resolve, reject) => {
console.log('doing sth'); //바로 실행됨
setTimeout( ()=> {
resolve('kim');
//reject(new Error('no network');
//Uncaught (in promise) Error : no network at promise.js:~~
}, 2000);
});
2. Consumer : then, catch, finally
promise
.then(value => {
//promise 정상 실행시 cb함수 resolve가 넘겨준 값을 value라는 파라미터로 받아옴
console.log(value);
//kim
})
.catch(error => {
//promise 실행 실패시 cb함수 reject가 넘겨준 값을 error라는 파라미터로 받아옴
console.log(error);
//Error : no network at promise.js:~~
});
.finally( () => {console.log('finally');
//앞의 then/catch와 상관없이 finally 무조건 출력
});
const fetchNumber = new Promise( (resolve, reject) =>{
setTimeout ( ()=> resolve(1), 1000);
//1초 있다가 숫자1을 반환
});
fetchNumber
.then(num => num*2) //2
.then(num => num*3) //6
.then(num => {
return new Promise( (resolve, reject) => {
setTimeout( ()=> resolve(num - 1), 1000);
//5
});
})
.then(num => console.log(num));
//5
//출력까지 걸리는 시간 2초
//이와 같이 비동기적인 놈들을 묶어서 처리할 수 있음
promise
.then(data => {
return fetchUser(data)
})
.then(user => {
sonole.log('User : ', user)
})
.catch(e => {
conosle.log("실패: ",e)
});
-then/catch 메서드가 또 다른 promise를 리턴하여 비동기 코드에 순서를 부여한다.
-함수를 호출한 주체가 함수를 끝낸 뒤 자기 자신을 리턴하도록 구현한다.
-then 체인을 이용해 비동기 처리 순서를 강제할 수도 있다
-new Promise를 생성하면 생성자는 내부적으로 return this라는 코드가 생략이 되어있다. then 또한 마찬가지이다. 이렇게 자기 자신을 return 하는 함수들이 있기 때문에 그 객체에 있는 .then/.catch 메서드가 연달아 호출될 수 있다.
Promise를 반환하는 함수를 만들어 호출할 필요 없이 바로 성공/실패 Promise를 호출이 가능함
-Promise.resolve() : 성공한 Promise를 바로 반환
-Promise.reject() : 실패한 Promise를 바로 반환
-인위적으로 Promise 메서드 체인 생성 가능
-비동기 코드로 진행해야 하는 상황 등에 유용하게 사용 가능
Promise '배열'을 받아 모두 성공하거나 하나라도 실패할 때까지 기다려 결과 반환
-Promise의 '배열'을 받아 모두 성공 시 각 Promise의 resolved 값을 배열로 반환
-하나의 Promise라도 실패시 가장 먼저 실패한 Promise의 실패 이유를 반환
-받는 배열이 promise가 아니라 일반 데이터일 수도 있음
async function sync() {
const r1 = await promise1();
const r2 = await promise2();
console.log(r1, r2);
}
---
async function parallel(){
const [r1, r2] = await Promise.all([
promise1(),
promise2(),
]);
console.log(r1,r2);
}
-promise1과 promise2는 각각 1초, 2초가 소요되는 비동기 함수.
-위의 함수에서는 3초의 시간이 소요.
-아래의 함수에서는 2초의 시간이 소요.
all의 세부적인 error 캐치를 위해서는
Promise.all([fetch().catch(err), fetch().catch(err), fetch().catch(err)])
try{
}
catch{
}
function doSomething(callback) {
console.log('[doSomething] I am doing something');
console.log('[doSomething] done!');
const payload = {
done: true,
};
callback(payload);
}
function doAnother(payload, callback) {
console.log('[doAnother] do something another task');
console.log('[doAnother] ok done!');
payload.flag = true;
callback(payload);
}
function main(callback) {
doSomething(function(payload) {
if (payload.done) {
doAnother(payload, function(payload) {
console.log('looks all good!');
callback(payload);
});
} else {
callback(payload);
}
});
}
main((payload) => {
console.log('all finished -', payload);
});
console.log('======================');
function doSomething2() {
return new Promise(resolve => {
console.log('[doSomething] I am doing something');
console.log('[doSomething] done!');
const payload = {
done: true,
};
resolve(payload);
});
}
function doAnother2(payload) {
console.log('[doAnother] do something another task');
console.log('[doAnother] ok done!');
payload.flag = true;
// return new Promise(resolve => resolve(payload));
return payload;
}
function main2() {
const result = doSomething2()
.then(payload => {
if (payload.done) {
return doAnother2(payload);
} else {
return payload;
}
})
.then(payload => {
console.log('looks all good!');
// return new Promise(resolve => resolve(payload));
return payload;
});
return result;
}
main2().then((payload) => {
console.log('all finished -', payload);
});
function tick(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
function test2() {
console.log('hello, wait 1s');
tick(1000)
.then(() => {
console.log('now wait 500ms');
})
.then(() => tick(500))
.then(() => {
console.log('ok wait 3s for the last');
})
.then(() => tick(3000))
.then(() => {
console.log('done!');
});
}
test2();
//test2와 test는 같은 값 반환
async function test() {
console.log('hello, wait 1s');
await tick(1000);
console.log('now wait 500ms');
await tick(500);
console.log('ok wait 3s for the last');
await tick(3000);
console.log('done!');
}
test();
-Promise를 활용한 비동기 코드를 간결하게 만들어주는 문법.
-async 함수와 await 키워드를 이용. await는 반드시 async 함수 안에서만 사용해야 한다.
-async로 선언된 함수는 반드시 Promise를 리턴한다.(return new Promise를 갖는다)
-모든 promise를 async/awiat로 표현할 수는 없다.
db.getUsers((err,users) =>{
if(err){
...
return;
}
async1(users, (r1) => {
async2(r1, (r2) => {
async3(r2, (r3)=>{
...
});
});
});
});
//위 코드는 async 1, 2, 3을 동기적으로 실행해야 할 때 나오는 코드, 콜백 지옥.
async function doSomething() =>{
const r1 = await promise1();
const r2 = await promise2(r1);
const r3 = await promise3(r1, r2);
//앞선 결과를 다음 함수에 간단하게 제공.
//이렇게 순차적이지 않은 promise의 순서를 async는 간결하게 표현 가능.
...
return r3;
});
doSomething().then(r3 => {
console.log(r3)
});
-async 함수 내에서 promise 함수의 결과는 await 으로 받을 수 있다.
-await한 promise 함수가 완료될 때 까지 다음 라인으로 넘어가지 않는다.
-순차적 프로그래밍처럼 작성 가능하다.
-async 함수는 Promise를 반환한다.
1)
function doSomething(num) {
return new Promise(resolve => {
console.log('do something');
resolve(num + 1);
});
}
function doAnother(num) {
return new Promise(resolve => {
console.log('do another');
resolve(num + 10);
});
}
function main() {
doSomething(0)
.then(doSomething)
.then(doSomething)
.then(doSomething)
.then(doSomething)
.then(doAnother)
.then(num => {
console.log('num', num);
});
}
//async를 통해 다음과 같이 간결하게 쓸 수 있음
async function main2() {
//- async 함수는 function 키워드 앞에 async를 붙여 만든다
let num = await doSomething(0);
//- async 함수 내부에서 await 키워드를 사용한다.
num = await doSomething(num);
//- await 키워드는 then 메서드 체인을 연결한 것처럼 순서대로 동작한다.
//따라서 비동기 코드에 쉽게 순서를 부여한다.
num = await doSomething(num);
num = await doAnother(num);
//- await는 resolve/reject처럼 데이터를 받을 때 까지 기다렸다가 실행한다.
//즉, 바로 실행되지 않는다.
console.log('num', num);
}
2)
//Promise
function p() {
return new Promise((resolve, reject) => {
resolve('hello');
// or reject(new Error('error');
});
}
p().then((n) => console.log(n)); //hello
//async await
async function p2(){
return 'hello';
}
p2().then((n) => console.log(n)); //hello
function p() {
return new Promise((resolve, reject) => {
let userName = getUserName("asdasd");
//서버에서 비동기적으로 받아오는 함수라는 가정
console.log('userName');//undefined출력. 이유는 데이터를 받기 전에 코드가 실행돼서.
//resolve('userName');
// 값 나옴. resolve로 값을 기다려서 받기때문.
});
}
p().then((n) => console.log(n));
async function p2(){
let userName = await getUserName("avdffds");
//await을 써주면 값을 받아올 때 까지 기다려줌.
console.log(userNAme); // 값 나옴, await로 값을 기다려서.
}
https://www.youtube.com/watch?v=JB_yU6Oe2eE&list=WL&index=18&t=728s
1) promise의 오류 처리
function fetchData1(){
return request()
.then( (res) => res.requestData)
.catch(error =>{console.log(error)})
}
2)async의 오류 처리
asnyc function async func(){
try {
let data1 = await fetchData1()
return fetchData2(data1)
} catch (e) {
console.log("error: ", e)
}
}
async function asyncFunc() {
try {
let data1 = await fetchData1()
return userName;
} catch (e) {
console.log("Name error: ", e)
}
try {
let data2 = await fetchData2()
return userAddress;
} catch (e) {
console.log("Address error: ", e)
}
}
-await으로 Promise 함수를 실행하면 Promise에서 reject 된 에러는 throw가 되기 때문에 try, catch 구문을 사용하여 오류를 처리할 수 있다.
//비동기 함수 : 2초 뒤에 Elice라는 이름을 인자로 받은 콜백함수의 인자로 넘겨준다.
function getName(cb) {
setTimeout(() => {
cb("Elice");
}, 2000);
}
//앞선 함수를 실행하려면 다음과 같이 getName 함수에 콜백함수를 넣어서 사용
getName((name) => {
console.log(name);
})
//~ 콜백함수 3개를 사용 할 경우
function getName(cb) {
setTimeout(() => {
cb("Elice");
}, 2000);
}
function getAge(cb) {
setTimeout(() => {
cb(6);
}, 2000);
}
function getAddress(cb) {
setTimeout(() => {
cb("Seoul");
}, 2000);
//c콜백 함수를 반복, 비동기 함수 3개, 2초씩 걸리므로 6초 뒤에 log가 나옴.(콜백지옥)
getName((name) => {
getAge((age) => {
getAddress((address) => {
console.log(name, age, address)
})
})
})
//// Promise ////
//Promise를 사용하여 콜백 지옥을 해결하기. 아래의 각 함수는 Promise 객체를 리턴.
function getName() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Elice");
}, 2000);
})
}
function getAge() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(6);
}, 2000);
})
}
function getAddress() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Seoul");
}, 2000);
})
}
//하지만 이렇게 사용하면 정보를 하나의 함수에서 제어하기 힘듦.
getName().then((res) => {
console.log(res);
})
getAge().then((res) => {
console.log(res);
})
getAddress().then((res) => {
console.log(res);
})
//따라서 promise.all은 첫번째 인자에 배열을 받는데 그 배열의 원소는 모두 프로미스 객체.
//getName, getAge, getAddress 함수는 모두 promise객체를 반환하기 때문에 Promise.all에서 사용가능하며 병렬적으로 배열의 원소에 속하는 모든 promise를 동시 실행.
//결과 적으로 2초 후에 log를 한 번에 출력. 콜백함수로는 불가능한 작업임.
Promise
.all([getName(), getAge(), getAddress()])
.then((res) => {
const [name, age, address] = res;
console.log(name, age, address)
})
//// Async Await ////
//즉시실행 함수 형태에 async 화살표 함수를 이용해 작성하여 프로미스를 더 간단하게 사용.
//await 키워드에서 프로미스가 resolve 될 때까지 기다린 후 다음 순서로 넘어가므로 6초 후에 log출력.
(async () => {
const name = await getName();
const age = await getAge();
const address = await getAddress();
console.log(name, age, address);
})();
-Promise와 async/await는 효과적으로 사용될 수 있는 상황이 다르기 때문에
두 방법 모두 잘 알고 있어야한다. 메소드 체이닝이 많이 사용되는 코드에서는 Promise가 코드에 일관성을 지켜서 더 깔끔하게 보일 수 있고, 개별 함수를 호출하여 값을 받아오는 경우에는 asyne/await이 효과적이다.
-cb 지옥은 promise chaining으로 해결, promise 지옥은 async-await으로 해결하면 된다.