[공부한 자료]

https://youtu.be/TAyLeIj1hMc  -> JavaScript - callback

https://youtu.be/Sn0ublt7CWM -> JavaScript - Promise

https://youtu.be/1z5bU-CTVsQ -> JavaScript - async & await

 

JS의 문법은 '비동기 처리'와 관련이 있다는 글을 정말 많이 보았다. 
'비동기 처리'란 브라우저를 지원하는 js의 언어 특성상, 코드를 실행하다 중간에 시간이 걸리더라도, 브라우저에 응답을 띄워야 하기 때문에 시간이 걸리는 부분에서 기다리지 않고 다음 코드를 실행하는 js의 문법 특성이다. 

 

비동기 처리

웹 브라우저와 서버는 서로 즉각적으로 통신해야 한다. 그러므로 만약에 js 언어로 통신하던 중 일부 코드에서 시간이 오래 걸리는 상황이 있을 경우, 해당 코드가 실행되기까지 계속 기다린다면 즉각적인 통신이 어려울 것이다. 따라서 시간이 추가적으로 걸리는(즉각적으로 반응하지 않는) 부분이 있을 경우, 그 부분과 이후 부분을 병렬적으로 처리하며 통신하는 방법을 사용한다. 이를 비동기 처리라고 한다. 

 

js에서 비동기 처리가 필요한 이유는 많다. 

예를 들어, 검색어를 입력하자마자 연관 검색어를 보여주는 기능의 경우, 서버와 웹 브라우저가 비동기적으로 통신하기 때문에 가능한 기능이다. 만약 비동기 처리가 없다면, 페이지를 새로고침하지 않으면 아무런 기능도 사용할 수 없었을 것이다. 

* 웹 브라우저와 서버는 js를 통해서 비동기 통신을 하는데, 이를 AJAX(Asynchronous Javascript And Xml)라고 한다. 

 

비동기 처리의 단점

다만 비동기 처리의 특성상, 예를 들어 함수 A가 먼저 실행되고 함수 B가 실행될 때, 만약 함수 B가 함수 A의 리턴 값을 필요로 하는데 함수 A가 값을 리턴하지 않았다면(실행이 완료되지 않았다면) 문제가 발생할 수 있다. 

JS에선 이러한 비동기 처리 상황에 대비해서 3가지의 방법(문법)이 있다. 

세 종류 모두 현재 사용하는 문법이며, 여러 문법을 같이 사용할 수도 있다. 


1) 콜백 함수(callback function)
2) Promise
3) async, await


세 가지 문법에 대해서 각각 살펴보자. 


1) callback function

함수 A가 다른 함수 B의 매개변수로 들어간 뒤 나중에 호출될 때, 함수 A를 callback function(나중에 호출된다는 의미)이라고 한다. 
js 언어만 callback이 가능한 것은 아니다. java 등 많은 언어에서 callback function을 사용할 수 있다. 
+ 사용할 수 있는 기준? 함수를 매개변수로 받을 수 있어야 한다. 

* but 변수의 값으로 함수를 받을 수 있는 대표적인 언어는 javascript이다. <-> java는 불가능

let funct = function(elem){
	console.log(elem);
};

 

callback function은 처음 보면 낯설 수 있다. 낯선 이유는 아마도:
1) 익명 함수에 익숙하지 않아서
2) arrow function 문법에 익숙하지 않아서(1번과 비슷)

그래서 오늘은 간단한 callback 함수 예시인 배열의 .filter 함수를 통해서 기존에 알고 있던 함수를 callback function으로 변환하는 과정을 살펴보겠다. 

 

[1]

함수 중에서는 다른 함수를 매개변수로 요구하는 함수가 있다. 이 경우, 우선 함수를 선언 및 정의한 뒤, 해당 함수를 매개변수로 필요로 하는 다른 함수에 넣어 주는 방식을 사용한다. 

const words = ['data', 'structure', 'algorithm', 'computer', 'math'];
let result;

function callback(element){
    return element.length > 6;
};

result = words.filter(callback);
console.log(result);

배열에서 사용하는 .filter 함수는 매개변수로 또 다른 함수를 필요로 한다. (이때, 이 함수는 반드시 true/false를 리턴하는 함수여야 한다는 조건이 있다.) 

따라서 callback이라는 함수를 사전에 정의한 뒤, filter 함수의 매개변수로 함수를 넣어줄 수 있다. 

 

[2]

그런데 위 함수처럼 정말 실행이 간단하거나, 한 번밖에 사용하지 않는 함수는 굳이 이름을 붙이고 선언하기 번거로울 때가 많다. 이 경우 '익명 함수'를 사용한다. 

익명 함수란, 함수를 선언할 때 붙이는 'function' 이라는 키워드를 붙이지 않고, 이름도 붙이지 않는 함수이다. 이름이 없고 function 키워드를 사용하여 선언하지 않았기 때문에 여러 번 재사용될 수 없다. 

result = words.filter((element)=>{
    return element.length > 6;
});
console.log(result);

앞서 선언된 callback 함수에서 function과 이름을 뺀 부분을 그대로 filter() 안의 매개변수에 넣었다. 

그리고 매개변수와 함수 몸체(body)를 구분하기 위해서 중간에 => 화살표를 선언해 주었다. 이를 arrow function이라고 한다. 

 

[3]

익명 함수의 매개변수가 딱 하나일 때는 매개변수를 둘러싼 괄호()를 제거할 수 있다. 

또한 함수 안의 코드가 한 문장일 때에는 중괄호{}도 생략할 수 있다. 

result = words.filter(word => word.length > 6);
console.log(result);

 

2) Promise

JS에서 표준화된 비동기 처리를 하기 위해 사용하는 객체이다. 

JS에서 비동기처리를 하는 함수는 Promise 타입의 인스턴스를 리턴한다. 바꿔 말하면, Promise 타입을 리턴하는 함수는 비동기 처리를 사용하는 함수라고 봐도 좋다. 

 

Promise로 어떻게 '표준화된' 비동기 처리를 할 수 있는 걸까?

Promise는 then()과 catch()라는 2개의 메소드를 통해서 비동기 처리를 한다. 

예를 들어, 비동기처리를 하는 함수 A가 있다. 

 

만약 A가 오류 없이 실행된다면, A는 Promise 타입의 인스턴스를 반환할 것이다. 

그럼 그 인스턴스를 .then() 메소드로 받아서 추가적인 작업을 할 수 있다. 

반면에 오류가 발생한다면, .catch()를 통해 오류를 받을 수 있다. (=오류를 매개변수로 받아서, 그에 대한 추가적인 작업/처리를 할 수 있다.)

 

재밌는 것은, then()과 catch() 모두 매개변수로 콜백함수를 받는다. 즉 비동기 함수가 실행된 뒤, then()이나 catch() 메소드를 통해서 비동기 처리가 오류 없이 진행되었을 때/또는 오류가 발생하였을 때 어떤 작업을 수행할 것인지를 then()이나 catch() 메소드에 함수를 매개변수로 넘겨줌으로써 알려줘야 한다. 

 

또한, then()의 메소드 안에서 return 문을 사용한다면, then()함수도 Promise 타입의 인스턴스를 리턴한다. 이렇게 되면 then() 뒤에 작성되는 함수에서도 또 .then()을 사용하여, 첫 번째 then() 메소드에서 리턴된 Promise에 대해서 추가로 작업을 할 수 있게 된다. 이를 Promise chaining이라고 한다. 

function asynchronous(){
	// asynchronous processing...
}

asynchronous()
	.then(result=>{
    	console.log(result.json); // result processing
    })
    .catch(err=>{
    	console.log(err.stack);	// error processing
    })

 

3) async & await

Promise는 함수 안에 함수가 복잡하게 내장된 콜백 지옥 문제를 해결할 수 있다는 장점이 있다. 그러나 Promise도 여러 번 사용한다면 코드가 복잡해질 수 있다. (여러 번의, 긴 promise chaining이 발생할 수 있다.)

따라서, js에서는 비동기적 코드도 동기적인 코드와 똑같이 작성할 수 있도록 하는 방법으로 async & await 문법을 만들었다. 

비동기적 처리를 하는 함수의 앞에 await 예약어(키워드)를 붙이고, await를 사용하는 해당 함수를 다른 함수로 감싼 뒤, 감싼 함수 앞에다 async 키워드를 붙이면 바로 사용할 수 있다. 

 

async 키워드를 붙인 함수는 Promise 인스턴스를 리턴한다. 또한 이 함수 안에서는 비동기 처리를 하는 함수가 포함되어 있음을 알 수 있다. 

즉 async 키워드는 '이 함수 안에는 비동기 처리를 하는 함수가 포함되어 있다'라고 선언하는 역할을 한다. 

그렇게 async 키워드로 선언을 하고 나서야 그 안에서 await을 사용하여, 비동기적 함수를 동기적인 방식으로(즉 순차적으로) 사용할 수 있는 것이다. 

async 키워드가 붙은 함수 안에서는, 순차적으로(=동기적으로) 코드가 실행된다. 

 

function timer(time){
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(time);
        }, time);
    });
}

async function run(){
    var time = await timer(1000);
    console.log('time: '+ time);
    time = await timer(1000+time);
    console.log('time: '+ time);
    time = await timer(1000+time);
    console.log('time: '+ time);
};

run();

 

또한, async & await 키워드와 promise를 같이 사용할 수도 있다. 

console.log('parent start');
run().then(()=>{
    console.log('parent end');
});

run() 함수는 promise를 리턴하는 비동기(async)함수이다. 그러므로 이 함수가 리턴하는 promise의 then() 메소드 안에 있는 코드는 run() 함수가 오류 없이 실행된 다음에, 순차적으로 실행된다. 

 

또한, async 함수 안에 return 문을 선언하여 async 함수에서 리턴 값을 받아서 변수에 할당할 수 있다. 

async 함수는 별도의 return문이 없는 경우 Promise 타입의 인스턴스를 반환하지만, return 문을 선언하는 경우 다른 값도 리턴할 수 있다. 다만 이 경우에도 함수 내에서는 비동기적 처리를 한다. 

async function run2(){
    var runTime = await run();
    console.log(`runtime: ${runTime}`);
};

run2();

 

+ Recent posts