동기(synchronous : 동시에 일어나는)
비동기(Asynchronous : 동시에 일어나지 않는)
비동기방식
은 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있는 장점이 있다.
동기
는 추구하는 같은 행위(목적)가 동시에 이루어지며, 비동기
는 추구하는 행위(목적)가 다를 수도 있고, 동시에 이루어지지도 않는다.
(출처: https://private.tistory.com/24)
<script>
function add10(a, callback) {
//인자를 받고(a), 함수가 일을 마쳤을 떄 결과를 전달할(callback)
setTimeout(() => callback(a + 10), 100); //100ms 딜레이
}
var a = add10(5, res => {
add10(res, res => {
add10(res, res => {
log(res);
});
});
});
</script>
똑같은 작업을 Promise
로 재구현
<script>
function add20(a) {
return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
}
var b = add20(5)
.then(add20)
.then(add20)
.then(log);
</script>
callback과 가장 큰 차이점
promise는 promise라는 클래스를 통해 만들어진 인스턴스를 반환하는데
대기
, 성공
, 실패
를 다루는 일급값으로 이루어져 있다.
방금 위에서 만든 a와 b를 콘솔에 찍어보자.
log(a);
log(b);
callback으로 생성된 변수는 undefined되나 / promise는 즉시 리턴된다.
<script>
const delay100 = a => new Promise(resolve =>
setTimeout(() => resolve(a), 100));
const go_promise = (a, f) => a instanceof Promise ? a.then(f) : f(a);
const add5 = a => a + 5;
var r = go_promise(10, add5);
log(r);
var r2 = go_promise(delay100(10), add5);
r2.then(log);
</script>
이는 아래의 식과 완전히 동일한 표현이다.
const n1 = 10;
go_promise(go_promise(n1, add5), log);
const n2 = delay100(10);
go_promise(go_promise(n2, add5), log);
근데 n1은 Promise가 아니라서 이 자체를 log찍어보면 undefined가 나오지만 n2는 Promise가 나온다.
const n1 = 10;
log(go_promise(go_promise(n1, add5), log));
const n2 = delay100(10);
log(go_promise(go_promise(n2, add5), log));
Promise를 사용하면 어떤 일을 한 결과의 상황을 일급값으로 만들어서 지속적으로 어떤 일들을 연결해 나갈 수 있다!
f · g
또는 f(g(x))
함수 합성
함수를 상황에 따라 안전하게 합성할 수 있게 하기 위해 모나드
라는 개념이 있고
비동기 상황을 안전하게 합성하는 것이 Promise
가령 이런 코드가 있을 때
<script type="module">
const g = a => a + 1;
const f = a => a * a;
log(f(g(1))); // 1 -> 4
log(f(g())); // 공백 -> ?
...
</script>
두 번째 로그는 공백에서 에러
가 뜨기 때문에 안전한 합성이라고 보기 어렵다. 안전한 인자가 들어와야 하는 함수라고 볼 수 있다. 값이 있는지 없는지 모를 때에도 안전하게 합성하려면? 모나드!
<script type="module">
const g = a => a + 1;
const f = a => a * a;
Array.of(1).map(g).map(f).forEach(r => log(r));
[].map(g).map(f).forEach(r => log(r));
Promise.resolve(2).then(g).then(f).then(r => log(r));
new Promise(resolve =>
setTimeout(() => resolve(2), 100)
).then(g).then(f).then(r => log(r));
</script>
원래는 빈 값이 들어와도 강제로 함수가 실행되니까 에러가 발생했으나
모나드 형태의 Array로 map을 통해서 함수를 합성하니까 안전하게 함수가 합성된다.
오류가 있을 수 있는 상황에서 안전하게 함수합성하는 규칙 중 하나
<script type="module">
// f . g
// f(g(x)) = f(g(x))
// f(g(x)) = g(x)
var users = [
{id: 1, name: 'aa'},
{id: 2, name: 'bb'},
{id: 3, name: 'cc'}
];
//user를 id값으로 찾는 함수를 만들자
const getUserById = id =>
find(u => u.id == id, users);
const f = ({name}) => name;
const g = getUserById;
const fg = id => f(g(id));
</script>
이 경우 users가 pop push되서 막~ 변할 때 대응하기 어렵다.
예를 들어 두 번의 pop으로 데이터가 id: 1
일 때만 남아있는데 fg(2)를 부르면 에러가 발생한다.
<script type="module">
var users = [
{id: 1, name: 'aa'},
{id: 2, name: 'bb'},
{id: 3, name: 'cc'}
];
const getUserById = id =>
find(u => u.id == id, users) || Promise.reject('???: 없어요!\n???: 아 있었는데?\n???: 아뇨 없어요.');
const f = ({name}) => name;
const g = getUserById;
const fg = id => Promise.resolve(id).then(g).then(f).catch(a => a);
fg(2).then(log);
users.pop();
users.pop();
fg(1).then(log);
setTimeout(function () {
users.pop();
users.pop();
fg(2).then(log);
}, 10);
</script>
<script>
go(1,
a => a + 10,
a => a + 1000,
a => a + 10000,
log);
</script>
fx.js
파일의 reduce를 다음과 같이 변경한다.
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
} else {
iter = iter[Symbol.iterator]();
}
let cur;
while (!(cur = iter.next()).done) {
const a = cur.value;
acc = f(acc, a);
}
return acc;
});
👇🏻
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
} else {
iter = iter[Symbol.iterator]();
}
return go1(acc, function recur(acc) {
let cur;
while (!(cur = iter.next()).done) {
const a = cur.value;
acc = f(acc, a);
if (acc instanceof Promise) return acc.then(recur);
}
return acc;
});
});
그리고 이 코드를 추가한다.
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
<script>
go(Promise.resolve(1),
a => a + 10,
a => Promise.resolve(a + 100),
a => a + 1000,
a => a + 10000,
log).catch(a => console.log(a));
</script>
<script>
go(Promise.resolve(1),
a => a + 10,
a => Promise.resolve(a + 100),
a => Promise.reject('error !'), //여기서 stop
a => console.log('----'), //출력되지 않는다
a => a + 1000,
a => a + 10000,
log).catch(a => console.log(a));
</script>