[JS] 비동기

jung_ho9 개발일지·2022년 11월 22일
0

JavaScript

목록 보기
13/19
post-thumbnail

동기와 비동기


동기

자바스크립트의 동기 처리란, 특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것을 의미한다.

비동기

자바스크립트의 비동기 처리는 ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것’을 의미한다.

JavaScript 작동원리


JavaScript는 싱글 스레드 기반으로 동작하는 언어이기 때문에 동기적으로 작동하게 된다. 그러나 JavaScript에서도 비동기 처리가 가능한 이유는 JavaScript가 작동하는 환경(런타임)에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리를 할 수 있게 된다.

JavaScript 내장함수


타이머 관련 API

setTimeout(callback, millisecond)

  • 일정 시간 후에 함수를 실행
  • 매개변수(parameter): 실행할 콜백 함수, 콜백 함수 실행 전 기다려야 할 시간 (밀리초)
  • return 값: 임의의 타이머 ID
setTimeout(function () {
  console.log('1초 후 실행');
}, 1000);

clearTimeout(timerId)

  • setTimeout 타이머를 종료
  • 매개변수(parameter): 타이머 ID
  • return 값: 없음
const timer = setTimeout(function () {
  console.log('10초 후 실행');
}, 10000);
clearTimeout(timer);

setInterval(callback, millisecond)

  • 일정 시간의 간격을 가지고 함수를 반복적으로 실행
  • 매개변수(parameter): 실행할 콜백 함수, 반복적으로 함수를 실행시키기 위한 시간 간격 (밀리초)
  • return 값: 임의의 타이머 ID
setInterval(function () {
  console.log('1초마다 실행');
}, 1000);

clearInterval(timerId)

  • setInterval 타이머를 종료
  • 매개변수: 타이머 ID
  • return 값: 없음
const timer = setInterval(function () {
  console.log('1초마다 실행');
}, 1000);
clearInterval(timer);

위에서 설명한 함수들을 사용하면 비동기적으로 실행은 되지만, 결과의 순서까지 제어할 수는 없다.
순서를 제어하고 싶다면? callback, promise, async/await 사용해야 한다.

내장함수 사용만으로는 결과의 순서까지 제어할 수 없다.

const printString = (string) => {
	setTimeout {
    	() => {
        	console.log(string);
        },
    }Math.floor(Math.random() * 100) +1
    )
}

const printAll = () => {
	printString("A")
	printString("B")
    printString("C"
}

printAll() 

코드의 실행 시간을 알 수 없기 때문에 결과는 랜덤하게 A,B,C 가 출력되고 있다.

Callback


callback을 사용하면 위에서 제어할 수 없었던 비동기 코드의 순서를 핸들링할 수 있게 된다.
A가 끝나면 callback 실행 B가 끝나면 callback 실행 ...의 반복으로 결과 순서가 ABC로 지켜진다.

const printString = (string, callback) => {
	setTimeout {
    	() => {
        	console.log(string);
            `callback()
        },
    }Math.floor(Math.random() * 100) +1
    )
}

const printAll = () => {
	printString("A", () => {
    	printString("B", () => {
        	printString("C", () => {})
        })
    })
}
printAll() 

Callback 인자


const somethinGonnaHappen = callback => {
  waitingUntilSomethinHappens();
  
  if(isSomethingGood) {  
	callback(null, data)
  }
  if(isSomethingBad) {
      callback(err, null)
  }
}

somethingGonnaHappen((err, data) => {
  	if(err) {
    	return;
    }
  	return data; 
})
  • 에러가 발생하지 않았다면 첫번재 인자는 null, 두번째 인자는 data를 넣어줌
  • 에러 발생 시 첫번째 인자는 error, 두번째 인자는 null을 넣어줌
  • 인자의 순서는 상관없는데 보통은 callback(err, data) 형식으로 사용

Callback HELL


콜백이 순차적으로 이루어지면 점점 코드의 가독성이 떨어지고 관리가 어려워지는데 이를 Callback Hell 이라고 한다.

const printAll = () => {
	printString("A", () => {
    	printString("B", () => {
        	printString("C", () => {})
            	printString("D", () => {
            		printString("E", () => {
                })
            })
        })
    })
}

Promise


  • 일종의 클래스로 new Promise()로 인스턴스 생성 가능하고 Promise의 생성자 안에는 resolve()와 reject() 가 포함되어 있다.
  • resolve() : 기능을 수행하고 마지막으로 최종 데이터를 전달한다.
  • reject() : 기능을 수행하다가 문제가 발생하면 호출한다.

Promise 특징 - 생성되는 순간 콜백실행


프로미스의 특징은 new Promise()로 인스턴스를 생성되는 그 순간 콜백함수의 안의 코드가 수행된다. 때문에 사용자가 요구한 순간에 네트워크 통신과 같은 비동기적 수행이 필요한거라면 아래와 같이 코드를 작성하면 불필요한 네트워크 통신이 일어나게 된다는 점을 유의해야 한다.

const Promise = new Promise ((resolve,reject) => {
	console.log('doing something...');
})

Promise 특징 - 상태


프로미스는 다음 중 하나의 상태를 가진다.

  • pennding : 이행하지도, 거부하지도 않은 초기 상태
  • fulfilled: 연산이 성공적으로 완료된 상태
  • reject : 연산이 실패한 상태

Promise 특징 - 내부 프로퍼티


new Promise 가 반환하는 Promise 객체는 내부 프로퍼디를 갖지만 직접 접근할 수 없고 .then, .catch, .finally 의 메서드를 사용해야 접근이 가능하다.

Then


executor에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve 함수를 호출하고 .then 메서드로 접근할 수 있다. 또한 .then 안에서 리턴한 값이 Promise면 Promise의 내부 프로퍼티 result를 다음 .then 의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 .then 의 콜백 함수의 인자로 받아올 수 있다.

let promise = new Promise((resolve, reject) => {
	resolve("성공");
});

promise.then(value => {
	console.log(value); // 성공 
})

Catch


executor에 작성했던 코드들이 에러가 발생했을 경우에는 reject 함수를 호출하고 .catch 메서드로 접근할 수 있다.

let promise = new Promise(function(resolve, reject) {
	reject(new Error("에러"))
});

promise.catch(error => {
	console.log(error); // Error: 에러
})

Finally


executor에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally 메서드로 접근할 수 있다.

let promise = new Promise(function(resolve, reject) {
	resolve("성공");
});

promise
.then(value => {
	console.log(value);
})
.catch(error => {
	console.log(error);
})
.finally(() => {
	console.log("성공이든 실패든 작동!");
})

Promise 특징 - Promise chaining


비동기 작업을 순차적으로 진행해야 하는 경우에 Promise chaining가 필요하다. Promise chaining이 가능한 이유는 .then, .catch, .finally 의 메서드들은 Promise를 반환하기 때문이다. 따라서 .then을 통해 연결할 수 있고, 에러가 발생할 경우 .catch 로 처리하면 된다.

let promise = new Promise(function(resolve, reject) {
	resolve('성공');
	...
});

promise
  .then((value) => {
    console.log(value);
    return '성공';
  })
  .then((value) => {
    console.log(value);
    return '성공';
  })
  .then((value) => {
    console.log(value);
    return '성공';
  })
  .catch((error) => {
    console.log(error);
    return '실패';
  })
  .finally(() => {
    console.log('성공이든 실패든 작동!');
  });

Promise.all()


Promise.all()은 여러 개의 프로미스를 처리할 때 사용하며, 모든 프로미스가 이행될 때까지 기다렸다 그 결과값을 담은 배열을 반환하는 메서드이다. 그 예로 복수의 URL에 동시에 요청을 보내고, 응답 완료를 기다릴 때 주로 사용한다.


async function pickFruits() {
  return Promise.all([getApple(), getBanana()])
  .then(fruits => fruits.join("+")
  );
}

Promise.race()


Promise.race()는 배열의 전달된 프로미스 중에서 가장 먼저 값을 리턴하는 아이만 출력된다.


async function pickFruits() {
  return Promise.race([getApple(), getBanana()]));
}

Promise HELL


return 처리를 제대로 해주지 못했을 때 promise도 똑같이 promise HELL이 발생하게 된다.

const printAll = () => {
	gotoCodestates()
    .then(data => {
    	sitAndCode()
        .the(data => {
      		getoBed() 
            .the(data => {
            	....!!!!!!!!
            })
        })
    })
}

resolve, reject 를 사용하지 않고 rerturn 하면 PromiseState 값이 출력된다.

async/await


JavaScript는 ES8에서 async/await키워드를 제공하여 복잡한 Promise 코드를 간결하게 작성할 수 있게 되었다. 함수 앞에 async 키워드와 함수 내에 await 키워드를 반드시 작성해주어야 하며 await 키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 된다.

// 함수 선언식
async function funcDeclarations() {
	await 작성하고자 하는 코드
	...
}

// 함수 표현식
const funcExpression = async function () {
	await 작성하고자 하는 코드
	...
}

// 화살표 함수
const ArrowFunc = async () => {
	await 작성하고자 하는 코드
	...
}

함수 앞에 async 키워드를 사용하면 코드 블럭이 자동으로 Promise를 생성한다.

async function getApple() {
 await delay(1000)
  return '사과';
}
async function getBanana() {
 await delay(1000)
  return '바나나';
}


async function pickFruits() {
  const apple = await getApple();
  const apple = await getBanana();
  return `${apple} ${banana}`
}

pickFruits().then(console.log)

async/await 예제로 위 코드의 pickFruits 함수를보면 await를 통해 getApple() 가 다 수행되어야 디음 getBanana() 가 시작될 수 있다. 하지만 위와 같이 코드를 작성한다면 getApple()과 getBanana() 받아오는 것은 서로 연관이 없는데 기다려야 하니까 비효율적일 수 있다.

때문에 이를 아래 코드와 같이 개선할 수 있다. 이처럼promise를 생성하면 안에 getApple() 과 getBanana() 가 바로 실행하기 때문에 병렬적으로 실행될 수 있다.


async function pickFruits() {
  const applePromise = getApple();
  const bananaPromise = getBanana();
  const apple = await applePromise;
  const banana = await bananaPromise;
  return `${apple} ${banana}`
}
profile
꾸준하게 기록하기

0개의 댓글