본문 바로가기

WEB개발/JS&HTML

[JS] JS의 비동기 작업 ( = promise, promiseAll)

 비동기란 특정 코드의 연산이 끝날 때까지 코드의 실행이 끝나지 않은 상태에서, 순차적으로 다음 코드를 먼저 실행하는 JS의 특성이다. 요청에 대한 결과를 기다리지 않고 다음 코드를 실행한다는 것이다. 

 

  • 대기(pending): 이행하거나 거부되지 않은 초기 상태.
  • 이행(fulfilled): 연산이 성공적으로 완료됨.
  • 거부(rejected): 연산이 실패함.

 

function asyncTask(success) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (success) {
                resolve("Task completed successfully!");
            } else {
                reject("Task failed!");
            }
        }, 2000); // 2초 후에 작업이 완료
    });
}

console.log("Task started...");

asyncTask(true)  // true를 전달하면 성공, false를 전달하면 실패
    .then(result => {
        console.log(result);  // 성공했을 때 실행
    })
    .catch(error => {
        console.error(error);  // 실패했을 때 실행
    })
    .finally(() => {
        console.log("Task finished");  // 성공 여부와 상관없이 실행
    });

 

  1. Pending (대기): 비동기 작업이 아직 완료되지 않은 상태.
  2. Fulfilled (성공): 비동기 작업이 성공적으로 완료된 상태.
  3. Rejected (실패): 비동기 작업이 실패한 상태.

 

이전까지 비동기화 작업은 주로 callback을 사용했다. 그러나 callback은 몇가지 문제가 있어 이를 보완하기 위해  promise가 등장합니다.

 

1. 비동기통신 응답 전달

 const get = url => {
	const xhr = new XMLHttpRequest() // XMLHttpRequest 객체생성
  	xhr.open('GET', url) // HTTP요청 초기화
  	xhr.send() // HTTP요청 전송
  
  	xhr.onload = () => { // onload 이벤트 핸들러 프로퍼티에 바인딩하고 종료
    	if (xhr.status === 200) {
      		return xhr.response
    	}
    	console.error(`${xhr.status} ${xhr.statusText}`)
	}
}

const response = get('https://woo06.tistory.com/6')
console.log(response) // undefined

response를 반환하지만 undefinded인 이유는 응답을 받기 전에 이미 console.log가 수행되기 때문입니다.

 

Promise를 이용한 수정코드

const get = url => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest(); // XMLHttpRequest 객체 생성
    xhr.open('GET', url); // HTTP 요청 초기화
    xhr.send(); // HTTP 요청 전송
    
    xhr.onload = () => { // onload 이벤트 핸들러 설정
      if (xhr.status === 200) {
        resolve(xhr.response); // 요청이 성공하면 resolve로 응답 반환
      } else {
        reject(new Error(`${xhr.status} ${xhr.statusText}`)); // 오류 발생 시 reject
      }
    };

    xhr.onerror = () => {
      reject(new Error('Request failed'));
    };
  });
};

get('https://woo06.tistory.com/6')
  .then(response => {
    console.log(response); // 응답이 성공적으로 도착하면 출력
  })
  .catch(error => {
    console.error('Error:', error); // 오류가 발생하면 출력
  });

 

 

2. 순차적 통신 수행

 

const get = (url, callback) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.send();
    
    xhr.onload = () => {
      if (xhr.status === 200) {
        callback(null, xhr.response); // 성공적으로 데이터를 가져온 경우
      } else {
        callback(new Error(`${xhr.status} ${xhr.statusText}`), null); // 오류 발생
      }
    };
  
    xhr.onerror = () => {
      callback(new Error('Request failed'), null); // 요청 실패
    };
  };
  
  // 여러 데이터를 순차적으로 받는 코드 (콜백 방식)
  get('https://woo06.tistory.com/1', (error, response1) => {
    if (error) {
      return console.error('Error fetching first response:', error);
    }
    console.log('First response:', response1);
  
    get('https://woo06.tistory.com/2', (error, response2) => {
      if (error) {
        return console.error('Error fetching second response:', error);
      }
      console.log('Second response:', response2);
  
      get('https://woo06.tistory.com/3', (error, response3) => {
        if (error) {
          return console.error('Error fetching third response:', error);
        }
        console.log('Third response:', response3);
      });
    });
  });

 

Promise를 이용한 수정코드

const get = url => {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.send();
      
      xhr.onload = () => {
        if (xhr.status === 200) {
          resolve(xhr.response);
        } else {
          reject(new Error(`${xhr.status} ${xhr.statusText}`));
        }
      };
  
      xhr.onerror = () => {
        reject(new Error('Request failed'));
      };
    });
  };
  
  // 여러 데이터를 순차적으로 받는 코드 (async/await)
  const fetchSequentialData = async () => {
    try {
      const response1 = await get('https://woo06.tistory.com/1');
      console.log('First response:', response1);
  
      const response2 = await get('https://woo06.tistory.com/2');
      console.log('Second response:', response2);
  
      const response3 = await get('https://woo06.tistory.com/3');
      console.log('Third response:', response3);
    } catch (error) {
      console.error('Error:', error);
    }
  };
  
  fetchSequentialData(); // 순차적으로 데이터를 가져오는 함수 호출

 

 


Promise.all

 

Promise.all은 JavaScript에서 Promise 객체를 처리할 때 사용하는 유틸리티 함수로, 여러 개의 Promise를 병렬로 실행하고, 모든 Promise가 성공적으로 이행(Fulfilled)되면 결과를 반환합니다. 이 과정에서 결과는 입력한 순서대로 반환됩니다.

 

 

const get = async (url) => {
	const response = await fetch(url); // 요청을 보냄
	if (!response.ok) {
		throw new Error(`HTTP error! status: ${response.status}`);
	}
	return response;
};


const fetchSequentialData = async () => {
	try {
		// 병렬로 요청 실행
		const [response1, response2, response3] = await Promise.all([
			get('https://woo06.tistory.com/2'),
			get('https://woo06.tistory.com/4'),
			get('https://woo06.tistory.com/5'),
		]);

		console.log('First response:', response1);
		console.log('Second response:', response2);
		console.log('Third response:', response3);
	} catch (error) {
		console.error('Error:', error);
	}
};

fetchSequentialData();

 

 

동작 방식

  1. 병렬 실행
  2. 결과 순서 보장
  3. 하나라도 실패하면 전체 실패

 주요 특징

  1. 병렬로 실행되지만 결과는 입력 배열 순서를 유지합니다.
  2. 최초 실패 시 즉시 reject됩니다.  첫 번째 실패한 Promise의 이유를 반환하며, 나머지 Promise의 상태는 알 수 없습니다.
  3. 입력값에 Promise가 아닌 값도 포함 가능
    Promise.all은 Promise가 아닌 값도 처리할 수 있으며, 해당 값은 자동으로 resolve된 것으로 간주합니다.
const promise1 = Promise.resolve(10); // 이미 이행된 Promise
const promise2 = new Promise((resolve) => setTimeout(() => resolve(20), 1000)); // 1초 후 이행
const nonPromiseValue = 30; // Promise가 아님

Promise.all([promise1, promise2, nonPromiseValue])
  .then((results) => {
    console.log(results); // [10, 20, 30]
  })
  .catch((error) => {
    console.error('Error:', error);
  });

 


Promise.allSettled

 

Promise.allSettled는 **모든 Promise가 이행(fulfilled)**되거나 **거부(rejected)**된 후에, 각각의 결과를 나타내는 객체를 반환하는 메서드입니다.

 

주요 특징

  1. 모든 Promise가 끝날 때까지 기다림:
    • Promise.allSettled는 Promise가 성공하거나 실패하더라도 모든 Promise가 완료될 때까지 기다립니다.
    • Promise.all과는 달리, 하나의 Promise가 거부되더라도 전체가 실패하지 않고 각각의 상태를 개별적으로 제공합니다.
  2. 결과 형식:
    • 결과는 배열로 반환되며, 각 항목은 다음 중 하나의 상태를 가집니다:
      • { status: 'fulfilled', value: ... }: 성공한 Promise.
      • { status: 'rejected', reason: ... }: 실패한 Promise.
const get = async (url) => {
	const response = await fetch(url); // 요청을 보냄
	if (!response.ok) {
		throw new Error(`HTTP error! status: ${response.status}`);
	}
	return response;
};

Promise.allSettled(
	[
		get('https://woo06.tistory.com/2'),
		get('https://woo06.tistory.com/4'),
		get('https://woo06.tistory.com/5'),
	]
).then((results) => {
	results.forEach((result, index) => {
		if (result.status === 'fulfilled') {
			console.log(`Promise ${index + 1} fulfilled with value: ${result.value}`);
		} else if (result.status === 'rejected') {
			console.log(`Promise ${index + 1} rejected with reason: ${result.reason}`);
		}
	});
});

 


 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
 

Promise - JavaScript | MDN

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

developer.mozilla.org

https://velog.io/@eunjin/JavaScript-Promise-%EB%93%B1%EC%9E%A5-%EB%B0%B0%EA%B2%BD-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EA%B8%B0%EB%B3%B8-%EB%A9%94%EC%84%9C%EB%93%9C-3%EA%B0%80%EC%A7%80-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

 

[JavaScript] 비동기 처리의 의미, 콜백 패턴의 문제점, Promise의 등장 배경

기능 구현만을 위해 백엔드 데이터를 받아오기 위해 기계적으로 썼던 fetch, then 등 자바스크립트의 비동기 처리를 위한 문법을 다시 들여다보고 기본을 다잡고자 정리해보려 한다. 프로미스란

velog.io

 

'WEB개발 > JS&HTML' 카테고리의 다른 글

[JS] Async, Await, Fetch, PromiseAll  (0) 2023.03.06
[JS, JAVA] 정규식  (0) 2022.06.13
[JS] Engine, Event Loop  (0) 2021.06.24
[html] input(inputmode, event, pattern)  (0) 2021.06.24
[JS] JavaScript 유용한 Array 함수 + JAVA stream()  (0) 2021.06.23