(ES6) ajax 위주의 promise 실습

들어가기에 앞서

집중력을 위해 짧은 글을 지향하여 Promise 문법을 설명하지는 않았다.
간단히 jQuery를 사용해본 사람이라면 쉽게 이해할 수 있을 것이다.
내가 찾아본 대부분의 Promise 관련 예제가 setTimeout 위주여서
ajax 위주의 예제를 한 번 정리해보았다.

비동기 코드를 동기식으로 작성하면?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const jsonURL = [
"https://perfectacle.github.io/mock/test.json",
"https://perfectacle.github.io/mock/test2.json"
];

const getDataAjax = url => {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "json";
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) { // 4 means request is done.
if(xhr.status === 200) { // 200 means status is successful
for(let key in xhr.response) { // 받아온 json 데이터의 키와 값의 쌍을 모두 출력.
if(xhr.response.hasOwnProperty(key))
console.log(`${key}: ${xhr.response[key]}`);
}
} else { // 통신 상에 오류가 있었다면 오류 코드를 출력.
console.error(`http status code: ${xhr.status}`);
}
}
};
xhr.send();
};

// 콘솔창을 키고 새로고침을 10번 정도 해보자.
// 콘솔창에 계속해서 동일한 결과가 출력되는가??
// 비동기 작업이기 때문에 순서를 보장하기 힘들다.
// 원래는 아래와 같이 나와야한다.
// test: hi
// test2: hi
// test: hi
// test: hi
// test2: hi
// test2: hi
// test: hi
// test2: hi
// test: hi
// test2: hi
getDataAjax(jsonURL[0]);
getDataAjax(jsonURL[1]);
getDataAjax(jsonURL[0]);
getDataAjax(jsonURL[0]);
getDataAjax(jsonURL[1]);
getDataAjax(jsonURL[1]);
getDataAjax(jsonURL[0]);
getDataAjax(jsonURL[1]);
getDataAjax(jsonURL[0]);
getDataAjax(jsonURL[1]);

비동기 코드를 비동기식(콜백 함수)으로 작성하면? (ES5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
const jsonURL = [
"https://perfectacle.github.io/mock/test.json",
"https://perfectacle.github.io/mock/test2.json"
];

// 따라서 아래와 같이 콜백 함수를 이용하여야 한다...
const getDataAjaxCallback = (url, res, rej) => {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "json";
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) { // 4 means request is done.
if(xhr.status === 200) { // 200 means status is successful
res(xhr.response); // 성공했을 시 실행할 콜백 함수.
} else {
rej(xhr.status); // 실패했을 시 실행할 콜백 함수.
}
}
};
xhr.send();
};

// 성공과 에러 콜백함수는 공통 함수로 빼버렸다.
const res = data => {
for(let key in data) {
if(data.hasOwnProperty(key))
console.log(`${key}: ${data[key]}`);
}
};
const rej = err => console.error(`http status code: ${err}`);

// 새로고침 10번을 해보고도 아래와 같은 순서로 나오는지 확인해보자.
// test: hi
// test2: hi
// test: hi
// test: hi
// test2: hi
// test2: hi
// test: hi
// test2: hi
// test: hi
// test2: hi
// 함수 안에 매개변수로 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[0], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[1], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[0], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[0], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[1], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[1], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[0], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[1], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[0], data => {
res(data);
// 순서를 보장하기 위해 콜백함수 안에 또 콜백함수가 들어가있다.
getDataAjaxCallback(jsonURL[1], data => {
res(data);
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err))
}, err => rej(err));

비동기식 코드의 순서를 아름답게 보장하려면…? (ES6)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
const jsonURL = [
"https://perfectacle.github.io/mock/test.json",
"https://perfectacle.github.io/mock/test2.json"
];

// 위 코드를 보면 가독성이 매우매우매우 안 좋다.
// 이를 위해 등장한 것이 promise.
// promise에 파라미터를 넘겨주기 위해선 밖에서 함수로 한 번 래핑해줘야 함.
const getDataAjaxPromise = url => (
// thenable하게 하기 위해 Promise 인스턴스를 리턴.
new Promise((res, rej) => {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "json";
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) { // 4 means request is done.
if(xhr.status === 200) { // 200 means status is successful
res(xhr.response); // 성공했을 시 실행할 콜백 함수.
} else {
rej(xhr.status); // 실패했을 시 실행할 콜백 함수.
}
}
};
xhr.send();
})
);

// 성공 콜백함수는 공통 함수로 빼버렸다.
const res = data => {
for(let key in data) {
console.log(`${key}: ${data[key]}`);
}
};

// 코드가 한결 우아해지지 않았는가?
// 새로고침 10번을 해보고도 아래와 같은 순서로 나오는지 확인해보자.
// test: hi
// test2: hi
// test: hi
// test: hi
// test2: hi
// test2: hi
// test: hi
// test2: hi
// test: hi
// test2: hi
getDataAjaxPromise(jsonURL[0])
.then(data => {
res(data);
// promise 인스턴스에 파라미터를 넘기기 위해선 다시 함수를 호출해야하고,
// thenable 하게 만들어야하기 때문에 함수의 반환값(프라미스 인스턴스)을 리턴해줘야함.
return getDataAjaxPromise(jsonURL[1]);
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[0])
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[0]);
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[1])
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[1])
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[0])
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[1])
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[0])
})
.then(data => {
res(data);
return getDataAjaxPromise(jsonURL[1])
})
// 더 이상 비동기로 처리할 내용이 없으므로 return문은 필요 없어짐.
.then(data => res(data))
.catch(err => console.error(`http status code: ${err}`));

마치며

콜백 함수를 Promise로 바꾸긴 했지만 그래도 좀 번거로운 건 어쩔 수 없을까…?
그에 대한 해답이라고 하긴 뭐하지만
(ES6+) ajax를 위한 fetch와 async/await에서 해결방안을 보자.