본문 바로가기

1.웹개발/JS

[JS]비동기 프로그래밍

결론 : 동기 프로그래밍은 동시에 발생하는 것이 아니다.

 

목표 : JS에서 비동기 프로그래밍의 이해

 


 

비동기 프로그래밍에 대해서 학습해보도록 하겠습니다.

 

목차

 

  1. 비+동기란?
    1-1 동기 
    1-2 비동기
    1-3 동기, 비동기 예시
    1-4 비동기의 방법과 단점(1) callback
    1-5 비동기의 방법 (2) promise
    1-6 비동기의 방법 (3) async / await
  2. 비동기 방식을 이용한 통신방법
    2-1 Ajax
    2-2 XMLHttpRquest
    2-3 fetch
    2-4 axios

 

 

 

비동기란?

 

비동기란, 非(아닐 비) 자를 사용하여, "동기가 아니다"라는 뜻입니다.

 

비동기를 알기위해선, 동기가 무엇인지 알아야 합니다. 동기가 아닌 것이 비동기 이니까요.

 

 

 

 

동기

 

그렇다면 동기(Synchronous)란?

 

同氣(같을 동, 기약할기) 학교 동기 할 때의 동기와 같은 단어를 사용합니다.

 

자체적으로 단어만 받아들였을때 장벽이 생기며

 

번역 하면

"동시 성의" 라는 뜻도 이해를 하는데 방해를 시킵니다. 포인트는 "형용사"라는 것입니다

 

뉘앙스를 포함하여 해석하면, "같은 시간에 행위를 하는 것(느낌)"입니다. 

 

싱크로나이즈

사진은 싱크로나이즈 사진이며, "다 같이 같은 시간에 동일한 행동"을 보여줘서 멋있게 보입니다.

 

여기서 싱크가 들어갔을때의 의미를 받아들여야 합니다. "동시에"라고 해석하면 쉽지만, 앞으로 이해가 가지 않습니다.

 

동기를 한 문장으로 표현하자면 "순서가 있는(보장되는)"으로 이해하시는게 좋다고 생각합니다.

 

지금까지 작성한 코드는, 동기적으로 사용되며 소스코드는 좌에서 우로, 위에서 아래로 흐름이 존재합니다.

 

console.log('1')
console.log('2')
console.log('3')

위의 결과를 예상해보세요. 1 -> 2 -> 3 순서대로 출력될 것이며 작성한 대부분은 동기적인(순서 보장) 코드입니다.

 

비동기

 

동기를 "순서가 있는(보장되는)"으로 정의하였으니, 비동기는 "순서가 없는" "또는 "순서가 보장되지 않는"으로 이해할 수 있겠습니다.

 

예를 살펴보겠습니다.

 

console.log('1')
setTimeout(() => {
    console.log('2')
},1000);
console.log('3')

출력의 흐름을 예상해보세요. 타이머 함수가 존재해서, 출력 결과가 변했다고 생각하실 수 있습니다.

 

console.log('1')
setTimeout(() => {
    console.log('2')
},0);
console.log('3')

지연시간을 0으로 변경해도(내부의 4ms의 최소 지연 시간은 있지만..) 결과는 동일합니다.

 

순서가 변경되었어요.


예시

A는 집안일해야 하는데 종류는 빨래와 설거지입니다. 빨래는 세탁기가, 설거지는 본인이 해야 하며 빨래는 3초, 설거지는 1초 걸린다고 가정하겠습니다.

 

A가 동기적이게 작업을 할 때입니다.

 

순서가 있기 때문에

 

세탁기를 돌려놓고 -> 기다린다 -> (3초 경과) - -> 설거지를 합니다 (1초) 

4초가 사용되었습니다.

 

A가 비동기적이게 작업을 할 때입니다.

 

세탁기를 돌려놓고 -> 설거지를 합니다 (1초)  -> 2초가 더 지나면 -> 세탁기가 끝납니다.

 

3초가 사용되었습니다.

 

일의 순서 변경과 대기시간을 활용하여, 시간을 감소시켰습니다.

 

소스 코드화 시켜보겠습니다.

 

//동기적인 A
function housework(delay){
    const start = Date.now();
    let limit = start + delay;
    while(Date.now() < limit){}
}

console.log("빨래 시작");
housework(3000);
console.log("빨래 끝");
console.log("설거지 시작");
housework(1000);
console.log("설거지 끝");
//비동기적인 A
console.log("빨래 시작");
houseWork(3000,"빨래")
console.log("설거지 시작")
houseWork(1000,"설거지")


function houseWork(delay, workname){
    setTimeout(() => {
        console.log(`${workname} 끝!`)
    },delay);
}

 

일을 처리하는 방식에 변경으로 얻는 장점은,

1번일 -> 1초
2번일 -> 1초
3번일 -> 100초
4번일 -> 1초

걸린다고 가정, 동기식이라면 4번 일은 102초를 기다려야 합니다.

비동기로 변경하면 4번 일을 3초 만에 결과를 얻을 수 있죠

 

 

const a = () => 'a'
const b = () => {
    let str;
    setTimeout(() => {
        str = 'hello wolrd';
    }, 1000)
    return str;
};
const c = () => 'c'

console.log(a());
console.log(b());
console.log(c());

또한, 비동기 문제로 인해 b 함수를 호출하였을 때 undefined를 출력하는 예제입니다


아직 할당되기 전에 출력이 되므로 문제가 생기게 됩니다.

 


비동기 방법과 단점(1) callback

 

그렇다면, 이렇게 훌륭한 비동기를 사용하는 방법에는 어떤 것이 있을까요?

 

배운 예제 중에는 addEventListener와 방금 배운 setTimeOut 같은 비동기 함수가 있습니다.

 

콜백 방식입니다. (콜백 함수에 대해 다른 포스트를 작성하겠습니다)

 

setTimeout(() => {
    alert("#");
},1000);


button.addEventListener('click', test)

setTimeout의 첫 번째 매개변수, 또는 addEventListener의 두 번째 매개변수를 보면 함수가 들어가 있습니다.

 

그러나 어디에도 "호출"되는 부분은 존재하지 않고, 특정 조건이 되었을 때만 동작합니다(시간이 지났거나, 클릭되었거나)

 

동기적인 코드보단 비동기적인 코드가 효율적이니까, 비동기 코드만 사용하면 될까요?

 

 

setTimeout(() => {
    console.log("첫번째 일")
},5000)
setTimeout(() => {
    console.log("두번째 일")
},3000)
setTimeout(() => {
    console.log("세번째 일")
},1000)
setTimeout(() => {
    console.log("네번째 일")
},2000)
setTimeout(() => {
    console.log("다섯번째 일")
},4000)

위의 코드를 보고,  어떤 순서로 일이 끝날지 알 수 있으신가요?

 

 

 

 

 

정답

 

 

 

 

비동기임에도 불구하고 순서대로 일이 끝나야 한다면 어떻게 해야 할까요?

 

setTimeout(() => {
    console.log("첫번째 일")
    setTimeout(() => {
        console.log("두번째 일")
        setTimeout(() => {
            console.log("세번째 일")
        },1000)
        setTimeout(() => {
            console.log("네번째 일")
            setTimeout(() => {
                console.log("다섯번째 일")
            },4000)
        },2000)
    },3000)
},5000)

 

일이 끝나면, 비동기 함수를 실행시키면 됩니다.

 

결과

잘 나와요, 문제는 가독성이 떨어져 유지보수에 어려움을 겪습니다. 이를 콜백 헬(callback hell) -> 콜백 지옥이라 합니다.

 


비동기의 방법  promise

 

콜백 방식의 문제점은 콜백 헬과, 콜백 코드들은 "성공"했을 때를 기준으로 삼습니다.

첫 번째 일에서 실패하거나 (세탁기가 고장 나서 빨래에 실패하면 -> 설거지를 진행하지 못합니다)하면

 

진행에 차질이 생기게 됩니다. 이를 보장해주기 위해 Promise(약속)이 등장했습니다.

 

무엇을 약속하나요? (성공 또는 실패에 대한 결괏값)을 주기로 약속받습니다.

 

코드를 통해 promise를 생성하고 사용해보도록 하겠습니다.

 

console.log(new Promise(() => {}))
// Promise { <pending> }

promise의 사용법은 new 키워드와 생성자 함수를 통해 promise 객체를 생성합니다.

단 생성자 함수를 호출할 때는 executor라는 (실행자, 실행 함수)를 자동으로 실행시킵니다.

 

그 후, executor함수에는 "자동"으로 매개 변수에, resolve와 reject라는 "성공/실패" 매개변수가 콜백으로 제공됩니다.

 

현재는 성공/실패 가 아닌 대기 상태입니다.

 

console.log(new Promise((resolve, reject) => {}))
// Promise { <pending> }

 

매개변수를 추가하였지만, 호출하지 않았기 때문에 대기 상태는 동일합니다.

 

promise 객체를 성공으로 해주고 싶을 때는 

 

console.log(new Promise((resolve, reject) => {
    resolve("성공")
}))
//Promise {<fulfilled>: '성공'}

resolve함수의 전달 인자에 작성해주면 되며, fulfilled(성공) 상태입니다.

 

console.log(new Promise((resolve, reject) => {
    reject("실패")
}))
//Promise {<rejected>: '실패'}

reject함수의 전달인자에 실패 시 발생할 결괏값을 작성해보면, rejected 상태는 실패된 상태입니다.

 

요약

1.pending(보류 상태) -> 성공/실패를 알지 못함

2.fulfilled(성공 상태) -> 성공

3.rejected(실패 상태) -> 실패

 

이렇게 생성자 함수를 통해 객체를 생성할 수 있고, 후속 메서드들을 통해 추가 작업이 가능합니다.

 

const promiseObj = new Promise((resolve, reject) => {
    resolve("성공")
})
//생성자 함수를 통해 생성된 promise 객체를 promiseObj 변수에 할당

사용하기 편하게 변수에 할당해두고

 

const promiseObj = new Promise((resolve, reject) => {
    resolve("성공")
})

promiseObj.then((result,error)=> {
    console.log(result)
	console.log(error)
    //성공
})


const promiseObj = new Promise((resolve, reject) => {
    reject("실패")
})

promiseObj.then((result,error)=> {
    console.log(result)
    console.log(error)
	//실패
})

then 메서드에는 자동으로 resolve시 전달받을 성공 값(result)과, reject시 전달받을 에러(error)를 매개변수로 받습니다.

 

then 메서드를 성공/실패 로만 나타낼 수 있도록 수정해보면

 

const promiseObj = new Promise((resolve, reject) => {
    resolve("성공")
})

// 성공시에만 동작할 then, 실패는 가정하지 않기 때문에 두번째 매개변수가 필요없습니다.
promiseObj.then((result)=> {
    console.log(result)
})


const promiseObj = new Promise((resolve, reject) => {
    reject("실패")
})

// 실패시에만 동작할 then 성공은 가정하지 않기 때문에 첫번째 매개변수에 명시적 부재를 넣었습니다.
promiseObj.then( null,error=> {
    console.log(error)
})

 

then 메서드에는(첫 번째 매개변수 -> 성공했을 때 넘어올 결괏값, 두 번째 매개변수 -> 실패했을 때 넘어올 에러 값)

 

가 넘어온다고 하였으며,  성공 실패로만 then을 변경하게 되면 실패 시에 코드의 가독성이 떨어집니다.

 

그래서 then대신, catch라는 메서드를 사용합니다.

 

 

const promiseObj = new Promise((resolve, reject) => {
    resolve("성공")
    resolve("실패")
})

// 성공시에만 동작할 then, 실패는 가정하지 않기 때문에 두번째 매개변수가 필요없습니다.
promiseObj.then((result)=> {
    console.log(result)
})




// 실패시에만 동작할 then 성공은 가정하지 않기 때문에 첫번째 매개변수에 명시적 부재를 넣었습니다.
promiseObj.catch((error)=> {
    console.log(error)
})

즉, then 메서드의 두 번째 매개 변수만 사용하지 않고 catch라는 후속처리 메서드를 사용하는 것입니다.

 

또한 finally에는 성공/실패를 구분하지 못하지만 마지막에 하는 "보편적인 작업"을 진행시키면 됩니다.

 

const promiseObj = new Promise((resolve, reject) => {
    resolve("성공")
    resolve("실패")
})

// 성공시에만 동작할 then, 실패는 가정하지 않기 때문에 두번째 매개변수가 필요없습니다.
promiseObj.then((result)=> {
    console.log(result)
})




// 실패시에만 동작할 then 성공은 가정하지 않기 때문에 첫번째 매개변수에 명시적 부재를 넣었습니다.
promiseObj.catch((error)=> {
    console.log(error)
})

promiseObj.finally(()=> {
    console.log("작업 다끝났어요")
})

//성공
//작업 다끝났어요

 

후속처리 메서드를 통해 promise를 사용할 수 있는 이유는, 후속처리 메서드들도 promise 객체를 리턴하기 때문이며 뒤에 나올 async/await와 axios 통신 ->  promise 기반입니다. promise에 대한 이해를 필요로 합니다.

 


 

promise 추가 자료입니다

 

https://hbsowo58.tistory.com/399

 

프로미스

프로미스는 자바스크립트에서 비동기 처리를 위한 패턴입니다. 최초 자바스크립트는 비동기 처리를 위해 콜백 패턴을 이용하였습니다. 문제점으로 가독성이 나쁘고, 비동기 처리 중 에러 처리

hbsowo58.tistory.com

 


비동기의 방법 (3) async / await

promise 방법은 비동기 프로그래밍을 보조하였습니다만, promise 객체를 사용하는 방법이 쉽지 않습니다

 

async / await를 통해 promise를 쉽게 사용하도록 하겠습니다

 

function houseWork(){
    const a = 1;
    console.log(a)
}
houseWork()
//1

일반적인 동기 함수입니다. 1이 출력됩니다. 만약 a에 할당하는 부분을 비동기로 수정하였다면?

 

 

function houseWork(){
	// ... 어떤 비동기작업 후
	const a = 1;
    // a에 1을 할당한다
    console.log(a)
}
houseWork()

 

순서가 보장이 안되니, a를 출력하는 시점에 a에 1이 할당되어 있다는 보장을 할 수 없습니다. 후에 다루겠습니다.

 

함수 앞에 async라는 키워드를 붙이면 비동기 함수가 됩니다. 또한 비동기 함수 내부에서 await를 작성하게 되면

 

순서를 보장해주게 됩니다.

 

async function houseWork(){
    const a = await 1;
    console.log(a);
}
houseWork()

a에 1을 할당하는 부분에서 대기상태를 거쳐, 값이 넘어온 경우에만 아래 로직을 진행합니다.

 

"비동기 함수"이나 가독성을 보았을 땐 "동기적"으로 동작하게 보조하는 키워드

 

내부적으론 promise로 구현되어있어서, return은 promise 객체이기 때문에 후속 처리 메서드를 사용할 수 있습니다.

 

뒤에 axios때 다루겠습니다.

 


비동기 방식을 이용한 통신방법 (1) Ajax

자바스크립트를 이용한 비동기 통신방법에는 Ajax가 있습니다.

 

Ajax 이전(2005년)에는 클라이언트가 서버에 요청을 보내면, 서버에서는 html 파일을 "통"으로 변경하였습니다.

 

클라이언트와 서버의 관계

Ajax 이전

 

1. 서버에 요청
2. HTML 파일 리턴

3. 리턴받은 파일 화면에 렌더링

 

 

-> 계속 HTML파일을 주고받고, 렌더링 하는 과정에, 깜빡이는 현상까지 발생하였습니다.

 

Ajax 이후

 

1.서버 요청(XMLHttpRequest)

2. 처리한 결과만 리턴

3. 필요한 부분만 수정

 

-> 깜빡이는 현상 제거 및 비동기이기 때문에 응답하는 동안 다른 작업도 진행 가능합니다.

 

 

 

Ajax란, Asynchronous JavaScript And XML의 약어이지만, XML이라는 확장 마크업보다는 모든 데이터에 작동하며,

 

JSON을 사용한다고 생각하시면 됩니다.

 

Ajax에 이어 나올 3가지 방법은 첫 번째 방법인 XMLHttpRequest를 사용한 방법의 확장입니다.

 


비동기 방식을 이용한 통신방법 (2) XMLHttpRequest

XMLHttpRequest 방법이란, HTTP 요청을 할 수 있는 내장 브라우저 "객체"입니다.

 

Ajax는 위 객체를 이용해서 서버와 비동기 통신을 하여 결과를 dom에 반영하는 기술이라고 할 수 있습니다.

 

중요 속성은 readyState -> 객체의 현재 상태 & status -> 서버로부터 응답받은 상태 및 리턴 메시지입니다.

 

중요속성 2가지

 

과정

1. 생성

2. 열기

3. 보내기

 

구분되어있으며, 서버로부터 응답받을 시 속성으로 결괏값이 넘어옵니다.

 

const httpRequest = new XMLHttpRequest();
console.log(httpRequest);
console.log(httpRequest.readyState);
httpRequest.onreadystatechange = function(){
    console.log('객체', httpRequest);
    console.log('객체 내 현 상태', httpRequest.readyState);
    if(httpRequest.readyState === httpRequest.DONE){
        if(httpRequest.status === 200 || httpRequest.status === 201){
          console.log(httpRequest.response);   
        }else{
        }
    }
}
httpRequest.open("GET", "https://jsonplaceholder.typicode.com/todos/");
httpRequest.send();

IDE 환경에서 확인을 해보면, 통신 성공과 객체 변화를 확인할 수 있습니다.

 


비동기 방식을 이용한 통신방법 (3) fetch

fetch란, XMLHttprequest 방법을 대체하는 방법입니다. 쉽고 서비스 워커 등 다른 기술을 사용할 수 있으며 promise 기반입니다.

 

fetch("https://jsonplaceholder.typicode.com/todos/")


//사용법이 간단하며, 내부적으로 promise로 이루어져 있기 때문에 then 메서드 체이닝이 가능하다

fetch("https://jsonplaceholder.typicode.com/todos/")
	.then(res => res.json())
	.then(res => console.log(res))

 

 


비동기 방식을 이용한 통신방법 (4) axios



axios에 대해서 학습하겠습니다. 내부적으로 promise로 구현된 http 방식입니다

 

https://jsonplaceholder.typicode.com/todos에 방문하게 되면 서버로부터 받을 json 데이터 양식을 볼 수 있습니다

 

console.log(axios.get("https://jsonplaceholder.typicode.com/todos"))

/*Promise
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
config: {transitional: {…}, transformRequest: Array(1), transformResponse: Array(1), timeout: 0, adapter: ƒ, …}
data: (200) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
headers: {cache-control: 'max-age=43200', content-type: 'application/json; charset=utf-8', expires: '-1', pragma: 'no-cache'}
request: XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
[[Prototype]]: Object*/

promise 객체를 get 메서드를 통해 가져오며, 성공이라 fulfilled 상태, PromiseResult에 객체 형태로 데이터를 담아옵니다.

 

promise 객체이기 때문에 후속처리 메서드가 사용 가능합니다.

 

 

const obj = axios.get("https://jsonplaceholder.typicode.com/todos")

obj.then(result => console.log(result))

 

jsonplaceholder 데이터

data에 접근해서 렌더링에 이용하시면 됩니다.

 

통신은 얼마나 걸릴지 모르기 때문에

 

async function getData(){

    const obj = await axios.get("https://jsonplaceholder.typicode.com/todos")
    console.log("데이터 받은 후 진행")
    console.log(obj)
}

getData()

async / await와 조합하면 서버에 데이터가 넘어올 때까지 기다린 후, 추가적인 작업을 하므로, 후속처리 메서드를 사용하지 않아도 됩니다.

 


 

반응형

'1.웹개발 > JS' 카테고리의 다른 글

[JS]Bom Bom Bom Bom이 왔네요  (0) 2022.09.28
[JS] 배열 for ... of 문  (0) 2022.04.26
변수와 메모리 적재 과정  (0) 2022.01.25
for문의 동작순서  (0) 2020.10.13
var와 let/const의 차이  (0) 2019.08.14