비동기 처리
•
특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트 특성
콜백
•
나중에 호출할 함수를 의미
비동기 동작
•
스크립트 함수 비동기 동작 처리 예시
◦
콜백함수 추가 전
function loadScript(src) {
// <script> 태그를 만들고 페이지에 태그를 추가합니다.
// 태그가 페이지에 추가되면 src에 있는 스크립트를 로딩하고 실행합니다.
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
// loadScript(src)는 <script src="…">를 동적으로 만들고 이를 문서에 추가
// 해당 경로에 위치한 스크립트를 불러오고 '비동기적으로' 실행
// 실행은 함수가 끝난 후
loadScript('/my/script.js');
// loadScript 아래의 코드는
// 스크립트 로딩이 끝날 때까지 기다리지 않습니다.
newFunction(); // 함수가 존재하지 않는다는 에러 발생
JavaScript
복사
◦
콜백함수 추가 후 - ‘콜백 기반(callback-based)’ 비동기 처리
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('/my/script.js', function() {
// 콜백 함수는 스크립트 로드가 끝나면 실행됩니다.
newFunction(); // 이제 함수 호출이 제대로 동작합니다.
...
});
JavaScript
복사
•
setTimeout : 스케줄링에 사용되는 대표적 함수
콜백 속 콜백
loadScript('/my/script.js', function(script) {
alert(`${script.src}을 로딩했습니다. 이젠, 다음 스크립트를 로딩합시다.`);
loadScript('/my/script2.js', function(script) {
alert(`두 번째 스크립트를 성공적으로 로딩했습니다.`);
loadScript('/my/script3.js', function(script) {
// 세 스크립트 로딩이 끝난 후 실행됨
});
});
});
JavaScript
복사
에러 핸들링
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생했습니다.`));
document.head.append(script);
}
JavaScript
복사
멸망의 피라미드
•
꼬리에 꼬리를 무는 비동기 동작이 많아졌을 때 다중 코드 중첩이 발생
프라미스
•
자바스크립트 비동기 처리에 사용되는 객체
프로미스의 3가지 상태
•
Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
◦
new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject가 됨.
new Promise(function(resolve, reject) {
// ...
});
JavaScript
복사
•
Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
◦
이행 상태가 되면 아래와 같이 then()을 이용하여 처리 결과 값을 받을 수 있다.
function getData() {
return new Promise(function(resolve, reject) {
var data = 100;
resolve(data);
});
}
// resolve()의 결과 값 data를 resolvedData로 받음
getData().then(function(resolvedData) {
console.log(resolvedData); // 100
});
JavaScript
복사
•
Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
◦
실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있다.
function getData() {
return new Promise(function(resolve, reject) {
reject(new Error("Request is failed"));
});
}
// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
console.log(err); // Error: Request is failed
});
JavaScript
복사
•
처리 흐름
가수와 팬, 구독 리스트
1.
'제작 코드(producing code)'는 원격에서 스크립트를 불러오는 것 같은 시간이 걸리는 일을 합니다. 위 비유에선 '가수’=executor가 제작 코드에 해당.
2.
'소비 코드(consuming code)'는 '제작 코드’의 결과를 기다렸다가 이를 소비. 이때 소비 주체(함수)는 여럿이 될 수 있습니다. 위 비유에서 소비 코드는 '팬’입니다.
3.
프라미스(promise) 는 '제작 코드’와 '소비 코드’를 연결해 주는 특별한 자바스크립트 객체
위 비유에서 프라미스는 '구독 리스트’입니다. '프라미스’는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 '제작 코드’가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 해줍니다.
•
프라미스 기본 문법
let promise = new Promise(function(resolve, reject) {
// executor (제작 코드, '가수')
});
JavaScript
복사
executor(실행자, 실행 함수)
new Promise에 전달되는 함수
•
자바스크립트 자체 콜백 executor 인수
◦
resolve(value) — 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
◦
reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
•
new Promise 생성자가 반환하는 promise 객체의 내부 Property
◦
state — 처음엔 "pending"(보류)이었다 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected".
◦
result — 처음엔 undefined이었다 resolve(value)가 호출되면 value로, reject(error)가 호출되면 error.
fulfilled promise(약속이 이행된 프라미스)
let promise = new Promise(function(resolve, reject) {
// 프라미스가 만들어지면 executor 함수는 자동으로 실행됩니다.
// 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 '완료'가 됩니다.
setTimeout(() => resolve("완료"), 1000);
});
JavaScript
복사
1.
executor는 new Promise에 의해 자동으로 그리고 즉각적으로 호출.
2.
executor는 인자로 resolve와 reject 함수를 받습니다. 이 함수들은 자바스크립트 엔진이 미리 정의한 함수이므로 개발자가 따로 만들 필요가 없습니다. 다만, resolve나 reject 중 하나는 반드시 호출 필요.
executor '처리’가 시작 된 지 1초 후, resolve("완료")이 호출되고 결과가 만들어집니다. 이때 promise 객체의 상태는 다음과 같이 변합니다.
settled promise(처리된 프라미스)
let promise = new Promise(function(resolve, reject) {
// 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냅니다.
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
JavaScript
복사
이행(resolved) 혹은 거부(rejected) 상태의 프라미스
then, catch, finally
소비함수 : .then, .catch, .finally
then
promise.then(
function(result) { /* 결과(result)를 다룹니다 */ },
function(error) { /* 에러(error)를 다룹니다 */ }
);
JavaScript
복사
•
첫 번째 인수
프라미스가 이행되었을 때 실행되는 함수 ex) 실행 결과
•
두 번째 인수
프라미스가 거부되었을 때 실행되는 함수 ex) 에러
•
작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달
catch
•
.catch(f) === .then(null,f)
•
에러가 발생하는 경우만 다루고 싶을 때 : .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: 에러 발생!" 출력
JavaScript
복사
finally
•
.finally(f) == .then(f, f)
.finally(f)가 문법측면에서 더 편리
•
then(f, f)와의 차이점
1.
finally 핸들러엔 인수 X. finally에선 프라미스가 이행되었는지, 거부되었는지 모름. finally에선 절차를 마무리하는 ‘보편적’ 동작을 수행하기 때문에 성공·실패 여부를 몰라도 됨.
2.
finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달
new Promise((resolve, reject) => {
setTimeout(() => resolve("결과"), 2000)
})
.finally(() => alert("프라미스가 준비되었습니다."))
.then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
})
.finally(() => alert("프라미스가 준비되었습니다."))
.catch(err => alert(err)); // <-- .catch에서 에러 객체를 다룰 수 있음
JavaScript
복사