정보실

웹학교

정보실

javascript 최신 JavaScript로 비동기 작업 작성

본문

이 기사에서는 과거의 비동기 실행에 대한 JavaScript의 진화와 코드 작성 및 읽기 방식이 어떻게 바뀌었는지 살펴 보겠습니다. 

웹 개발의 시작부터 시작하여 현대적인 비동기 패턴 예제로 넘어갑니다.


https://www.smashingmagazine.com/2019/10/asynchronous-tasks-modern-javascript/ 


JavaScript는 코드의 작동 방식을 이해하는 데 중요한 프로그래밍 언어로서 두 가지 주요 특징이 있습니다. 첫 번째는 동기적 특성으로, 코드를 읽을 때마다 코드가 한 줄씩 실행되고 두 번째 코드는 단일 스레드 이므로 언제든지 하나의 명령 만 실행됩니다.


언어가 발전함에 따라 새로운 실행물이 등장하여 비동기 실행이 가능해졌습니다. 개발자들은 더 복잡한 알고리즘과 데이터 흐름을 해결하면서 다양한 접근 방식을 시도했으며, 그 결과 새로운 인터페이스와 패턴이 등장했습니다.


동기 실행 및 관찰자 패턴 


소개에서 언급했듯이 JavaScript는 대부분 한 줄씩 작성하는 코드를 실행합니다. 처음 몇 년 동안 이 언어는 이 규칙에 예외가 있었지만 몇 가지 였지만 HTTP 요청, DOM 이벤트 및 시간 간격 등 이미 알고 있을 것입니다.


const button = document.querySelector('button');


// observe for user interaction

button.addEventListener('click', function(e) {

  console.log('user click just happened!');

})


이벤트 리스너 (예 : 요소 클릭)를 추가하고 사용자가 이 상호 작용을 트리거하면 JavaScript 엔진은 이벤트 리스너 콜백을 위해 작업을 대기열에 추가하지만 현재 스택에 있는 것을 계속 실행합니다. 현재 통화가 완료되면 리스너의 콜백이 실행됩니다.

이 동작은 웹 개발자를 위한 비동기 실행에 액세스하는 최초의 아티 팩트인 네트워크 요청 및 타이머에서 발생하는 것과 유사합니다.

JavaScript에서 일반적인 동기 실행의 예외는 되었지만 언어는 여전히 단일 스레드이며 tak을 대기열에 넣고 비동기적으로 실행한 다음 기본 스레드로 돌아갈 수 있음을 이해하는 것이 중요합니다. 단 한 코드만 실행할 수 있습니다. 한 번에.

예를 들어 네트워크 요청을 확인하겠습니다.

var request = new XMLHttpRequest();
request.open('GET', '//some.api.at/server', true);

// observe for server response
request.onreadystatechange = function() {
  if (request.readyState === 4 && xhr.status === 200) {
    console.log(request.responseText);
  }
}

request.send();

서버가 다시 돌아 오면 onreadystatechange에 지정된 메소드에 대한 태스크가 대기됩니다 ​​(코드 실행은 기본 스레드에서 계속됨).

참고 : JavaScript 엔진이 작업을 대기열에 넣고 실행 스레드를 처리하는 방법을 설명하는 것은 복잡한 주제이며 기사 자체를 다루는 것이 좋습니다. 그래도 필립 로버츠 (Phillip Roberts)의“The The Heck Is The Event Loop?

언급 된 각 경우에 외부 이벤트에 응답하고 있습니다. 특정 시간 간격, 사용자 조치 또는 서버 응답에 도달했습니다. 우리는 비동기 작업 자체를 만들 수 없었으며, 항상 도달 범위 밖에서 발생하는 사건을 관찰했습니다.

이러한 방식으로 형성된 코드를 관찰자 패턴이라고 하는 이유는 이 경우 addEventListener 인터페이스로 더 잘 나타납니다. 곧 이벤트 이미터 라이브러리 또는 프레임 워크가 이 패턴을 드러내는 프레임 워크가 번성했습니다.

노드 JS와 이벤트 이미터 (EVENT EMITTERS) 

좋은 예로는 Node.js가 있는데,이 페이지는 "비동기 이벤트 중심 JavaScript 런타임"이라고 설명하므로 이벤트 이미터와 콜백은 일류 시민이었습니다. 심지어 EventEmitter 생성자가 이미 구현되어 있습니다.

const EventEmitter = require('events');
const emitter = new EventEmitter();

// respond to events
emitter.on('greeting', (message) => console.log(message));

// send events
emitter.emit('greeting', 'Hi there!');

이것은 비동기 실행을 위한 이동 접근 방식 일뿐 아니라 생태계의 핵심 패턴과 규칙입니다. Node.js는 웹 외부에서도 다른 환경에서 JavaScript를 작성하는 새로운 시대를 열었습니다. 결과적으로 새 디렉토리 작성 또는 파일 작성과 같은 다른 비동기 상황이 가능했습니다.

const { mkdir, writeFile } = require('fs');

const styles = 'body { background: #ffdead; }';

mkdir('./assets/', (error) => {
  if (!error) {
    writeFile('assets/main.css', styles, 'utf-8', (error) => {
      if (!error) console.log('stylesheet created');
    })
  }
})

콜백은 첫 번째 인수로 오류를 수신하고 응답 데이터가 예상되면 두 번째 인수로 진행됩니다. 이를 오류 우선 콜백 패턴이라고 하며, 이는 작성자와 기고자가 자체 패키지 및 라이브러리에 채택한 규칙이 되었습니다.

약속(Promises)과 끝없는 콜백 체인 

웹 개발이 더 복잡한 문제를 해결함에 따라 더 나은 비동기 아티팩트가 필요했습니다. 마지막 코드 스니펫을 보면 반복되는 콜백 체인을 볼 수 있으며 작업 수가 증가함에 따라 확장되지 않습니다.

예를 들어 파일 읽기 및 스타일 사전 처리라는 두 단계 만 더 추가해 보겠습니다.

const { mkdir, writeFile, readFile } = require('fs');
const less = require('less')

readFile('./main.less', 'utf-8', (error, data) => {
  if (error) throw error
  less.render(data, (lessError, output) => {
    if (lessError) throw lessError
    mkdir('./assets/', (dirError) => {
      if (dirError) throw dirError
      writeFile('assets/main.css', output.css, 'utf-8', (writeError) => {
        if (writeError) throw writeError
        console.log('stylesheet created');
      })
    })
  })
})

우리가 작성하는 프로그램이 더 복잡 해짐에 따라 다중 콜백 체인 및 반복적 인 오류 처리로 인해 사람의 눈에 따라 코드를 따르기가 어려워지는 것을 알 수 있습니다.

약속, 포장지 및 체인 패턴(PROMISES, WRAPPERS AND CHAIN PATTERNS) 

JavaScript 언어에 새로 추가 된 제품으로 처음 발표되었을 때 많은 관심을 받지 못했지만 다른 언어가 수십 년 전에 비슷한 구현을 했기 때문에 새로운 개념은 아닙니다. 사실, 그들은 출연 한 이후로 작업 한 대부분의 프로젝트의 의미와 구조를 많이 바꾸는 것으로 나타났습니다.

약속은 개발자가 비동기 코드를 작성할 수 있는 기본 제공 솔루션을 도입했을 뿐만 아니라 웹 페치의 최신 기능인 페치 (fetch)와 같은 새로운 기반을 제공하는 웹 개발의 새로운 단계를 열었습니다.

콜백 방식에서 약속 기반 방식으로 메소드를 마이그레이션 하는 것은 프로젝트 (예 : 라이브러리 및 브라우저)에서 점점 더 일반화되었으며 Node.js조차도 천천히 마이그레이션 하기 시작했습니다.

예를 들어, Node의 readFile 메소드를 래핑하겠습니다.

const { readFile } = require('fs');

const asyncReadFile = (path, options) => {
  return new Promise((resolve, reject) => {
    readFile(path, options, (error, data) => {
      if (error) reject(error);
      else resolve(data);
    })
  });
}

여기에서는 Promise 생성자 내에서 실행하고 메서드 결과가 성공하면 resolve를 호출하고 오류 객체가 정의되면 거부하여 콜백을 숨깁니다.

메소드가 Promise 객체를 반환하면 함수를 전달하여 성공적인 해결을 따를 수 있습니다. 인수는 약속이 해결 된 값 (이 경우 데이터)입니다.

메소드 중에 오류가 발생하면 catch 함수가 호출됩니다 (있는 경우).

참고 : 약속의 작동 방식에 대해 보다 깊이 이해하려면 Jake Archibald의 "자바 스크립트 약속 : 소개"기사를 Google 웹 개발 블로그에 작성하는 것이 좋습니다.

이제 이러한 새로운 메소드를 사용하고 콜백 체인을 피할 수 있습니다.

asyncRead('./main.less', 'utf-8')
  .then(data => console.log('file content', data))
  .catch(error => console.error('something went wrong', error))

비동기 작업을 생성하는 기본 방법과 가능한 결과를 추적 할 수 있는 명확한 인터페이스를 제공함으로써 업계는 관찰자 패턴에서 벗어날 수 있었습니다. 약속 기반의 코드는 읽을 수 없고 오류가 발생하기 쉬운 코드를 해결하는 것처럼 보였습니다.

더 나은 구문 강조 표시 또는 더 명확한 오류 메시지는 코딩하는 동안 도움이 되므로, 추론하기 쉬운 코드는 개발자가 코드를 읽는 것이 더 예측 가능해지며, 실행 경로를 더 잘 파악하면 가능한 함정을 쉽게 포착 할 수 있습니다.

Promise 채택은 커뮤니티에서 전 세계적으로 널리 채택되어 Node.js가 내장 된 버전의 I / O 메소드를 빠르게 릴리스하여 fs.promises에서 파일 작업 가져 오기와 같은 Promise 객체를 반환합니다.

또한 오류 우선 콜백 패턴을 따르는 모든 함수를 래핑하고 이를 Promise 기반의 함수로 변환하기 위한 유망한 유틸리티를 제공했습니다.

그러나 약속은 모든 경우에 도움이 됩니까? 

Promises로 작성된 스타일 전처리 작업을 다시 상상해 봅시다.

const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less')

readFile('./main.less', 'utf-8')
  .then(less.render)
  .then(result =>
    mkdir('./assets')
      .then(writeFile('assets/main.css', result.css, 'utf-8'))
  )
  .catch(error => console.error(error))

코드에서 특히 중복 처리가 명확하게 줄어 들었습니다. 특히 오류 처리와 관련하여 특히 오류 처리와 관련이 있지만 약속은 동작의 연결과 직접적으로 관련된 명확한 코드 들여 쓰기를 제공하지 못했습니다.

이것은 readFile이 호출 된 후 첫 번째 then 문에서 실제로 달성됩니다. 이 줄 다음에 발생하는 일은 먼저 디렉토리를 만들어서 결과를 파일에 쓸 수있는 새로운 범위를 만들어야 한다는 것입니다. 이로 인해 들여 쓰기 리듬이 깨져서 지침 순서를 한눈에 쉽게 결정할 수 없습니다.

이를 해결하는 방법은 이를 처리하고 메소드를 올바르게 연결할 수있는 사용자 정의 메소드를 사전에 굽는 것입니다. 그러나 우리는 이미 태스크를 달성하는 데 필요한 것으로 보이는 코드에 한층 더 복잡한 심도를 도입 할 것입니다 우리는 원한다.

참고 :이 프로그램은 예제 프로그램으로 간주되며 일부 방법을 관리하고 있으며 모두 업계 규칙을 따르지만 항상 그런 것은 아닙니다. 더 복잡한 연결이나 다른 모양의 라이브러리를 도입하면 코드 스타일이 쉽게 깨질 수 있습니다.

다행스럽게도 JavaScript 커뮤니티는 다른 언어 구문에서 다시 배우고 비동기 작업 연결이 동기 코드처럼 읽기가 쉽지 않거나 읽기가 쉽지 않은 경우에 도움이 되는 표기법을 추가했습니다.

비동기와 기다림 (Async And Await)

Promise는 실행시 확인할 수 없는 값으로 정의되며 Promise 인스턴스를 만드는 것은 이 아티팩트를 명시 적으로 호출합니다.


const { mkdir, writeFile, readFile } = require('fs').promises;

const less = require('less')


readFile('./main.less', 'utf-8')

  .then(less.render)

  .then(result =>

    mkdir('./assets')

      .then(writeFile('assets/main.css', result.css, 'utf-8'))

  )

  .catch(error => console.error(error))


비동기 메서드 내에서 await reserved 단어를 사용하여 Promise의 실행을 결정한 후 실행을 계속할 수 있습니다.


이 구문을 사용하여 코드 조각을 다시 방문해 보겠습니다.


const { mkdir, writeFile, readFile } = require('fs').promises;

const less = require('less')


async function processLess() {

  const content = await readFile('./main.less', 'utf-8')

  const result = await less.render(content)

  await mkdir('./assets')

  await writeFile('assets/main.css', result.css, 'utf-8')

}


processLess()


참고 : 오늘날 비동기 함수 범위 밖에서 대기를 사용할 수 없으므로 모든 코드를 메소드로 이동해야 합니다.


비동기 메서드가 await 문을 찾을 때마다 진행 값 또는 약속이 해결 될 때까지 실행이 중지됩니다.


비동기 실행에도 불구하고 비동기 / 대기 표기법을 사용하면 명확한 결과를 얻을 수 있습니다. 코드는 동기적인 것처럼 보이며, 이는 개발자가 더 자주보고 추론하는 데 사용됩니다.


오류 처리는 어떻습니까? 그것을 위해, 우리는 언어로 오랫동안 존재했던 진술을 사용하고 시도하고 붙잡습니다.


const { mkdir, writeFile, readFile } = require('fs').promises;

const less = require('less');


async function processLess() {

  try {

    const content = await readFile('./main.less', 'utf-8')

    const result = await less.render(content)

    await mkdir('./assets')

    await writeFile('assets/main.css', result.css, 'utf-8')

  } catch(e) {

    console.error(e)

  }

}


processLess()


프로세스에서 발생하는 모든 오류는 catch 문 내부의 코드에 의해 처리됩니다. 우리는 오류 처리를 담당하는 중심적인 위치에 있지만 이제는 읽고 이해하기 쉬운 코드가 있습니다.


결과 값을 리턴 하는 조치가 코드 리듬을 위반하지 않는 mkdir과 ​​같은 변수에 저장 될 필요는 없습니다. 나중 단계에서 결과 값에 액세스하기 위해 새 범위를 만들 필요도 없습니다.


약속은 언어로 도입 된 기본 아티팩트라고 말하면 안전합니다. JavaScript에서 비동기 / 대기 표시를 사용하려면 최신 브라우저와 최신 버전의 Node.js에서 모두 사용할 수 있습니다.


참고 : 최근 JSConf에서 Node의 작성자이자 첫 번째 기여자 인 Ryan Dahl은 초기 개발에 대한 약속을 지키지 않은 것을 후회했습니다. Node의 목표는 Observer 패턴이 더 나은 이벤트 중심 서버 및 파일 관리를 만드는 것이기 때문입니다.


결론 


웹 개발 환경에 대한 약속의 도입은 코드에서 작업을 대기열에 넣는 방식을 변경하고 코드 실행에 대한 추론 방법과 라이브러리 및 패키지를 작성하는 방법을 변경했습니다.


그러나 콜백 체인에서 멀어지면 해결하기가 더 어렵습니다. 그런 다음 방법을 통과해야 수년간 옵저버 패턴과 주요 공급 업체가 채택한 접근 방식에 익숙해 진 후에도 생각의 기차에서 멀어지지 않았다고 생각합니다. Node.js와 같은 커뮤니티에서


놀란 로손 (Nolan Lawson)은 약속 연결에서 잘못된 사용에 대한 훌륭한 기사에서 오래된 콜백 습관은 힘들다고 말합니다. 그는 나중에 이러한 함정을 피하는 방법을 설명합니다.


비동기식 작업을 생성하는 자연스러운 방법을 허용하는 중간 단계로 약속이 필요하다고 생각하지만 더 나은 코드 패턴을 개발하는 데 큰 도움이 되지는 않았지만 때로는 더 적응력 있고 개선 된 언어 구문이 필요합니다.


우리는 웹 외부에서 항상 JavaScript 거버넌스를 확장하고 더 복잡한 퍼즐을 풀려고 노력함에 따라 ECMAScript 사양이 몇 년 안에 어떻게 보일지 아직 모릅니다.


이러한 퍼즐 중 일부가 더 간단한 프로그램으로 바뀌기 위해 언어에서 정확히 무엇이 필요한지 지금 말하기는 어렵지만, 웹과 JavaScript 자체가 문제를 해결하고 도전과 새로운 환경에 적응하려고 노력하는 방식에 만족합니다. 저는 지금 JavaScript가 10 년 전에 브라우저에서 코드를 작성하기 시작했을 때보다 비동기적인 환경이라고 생각합니다.



  • 트위터로 보내기
  • 페이스북으로 보내기
  • 구글플러스로 보내기
  • 카카오톡으로 보내기

페이지 정보

조회 12회 ]  작성일19-10-30 15:18

웹학교