정보실

웹학교

정보실

javascript 3 장. JavaScript 엔진의 회람 투어

본문

멋진 JavaScript 엔진 세계를 소개하여 언어에 대한 몰입을 시작합시다.


브라우저가 JavaScript 코드를 읽고 실행하는 방법에 대해 궁금한 적이 있습니까? 

그것은 마술처럼 보이지만 후드 아래에서 일어나는 일에 대한 힌트를 얻을 수 있습니다. 

Chrome에서 브라우저 콘솔을 열고 소스 탭을 살펴보십시오. 더 흥미로운 콜 스택 중 하나 인 일부 상자가 표시됩니다 (Firefox에서는 코드에 중단점을 삽입 한 후 콜 스택을 볼 수 있음).


Call Stack 


https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter3.md 


콜 스택이란 무엇입니까? 

몇 줄의 코드를 실행하더라도 많은 일이 진행되는 것처럼 보입니다. 실제로 모든 웹 브라우저에서 JavaScript가 기본적으로 제공되는 것은 아닙니다. JavaScript 코드를 컴파일하고 해석하는 큰 구성 요소가 있습니다. 이것이 JavaScript 엔진입니다. 

가장 많이 사용되는 JavaScript 엔진은 Google Chrome 및 Node.js에서 사용하는 V8, Firefox 용 SpiderMonkey 및 Safari / WebKit에서 사용하는 JavaScriptCore입니다. 

오늘날 JavaScript 엔진은 훌륭한 엔지니어링 기술이며 모든 측면을 다루는 것은 불가능합니다. 그러나 우리를 위해 열심히 일하는 모든 엔진에는 작은 조각이 있습니다. 이러한 구성 요소 중 하나는 Call Stack이며 Global Memory 및 Execution Context와 함께 코드를 실행할 수 있습니다. 그들을 만날 준비가 되셨습니까?


자바 스크립트 엔진 및 글로벌 메모리 


JavaScript는 동시에 컴파일 된 언어와 해석 된 언어 모두라고 말했습니다. JavaScript 엔진은 실제로 코드를 실행하기 전에 마이크로 초 단위로 코드를 컴파일합니다. 마술 같지요? 이 마법을 JIT (Just in Time Compiling)라고 합니다. 자체적으로 큰 주제이므로 다른 책으로는 JIT 작동 방식을 설명하기에 충분하지 않습니다. 그러나 지금은 컴파일 배후의 이론을 건너 뛰고 실행 단계에 집중할 수 있습니다. 그럼에도 불구하고 흥미롭습니다.


시작하려면 다음 코드를 고려하십시오.


var num = 2;

function pow(num) {
    return num * num;
}


위의 코드가 브라우저에서 어떻게 처리되는지 묻습니다. "브라우저가 코드를 읽습니다"또는 "브라우저가 코드를 실행합니다"라고 말할 수 있습니다. 현실은 그것보다 미묘한 차이가 있습니다. 첫째, 코드 스니펫을 읽는 것은 브라우저가 아닙니다. 엔진입니다. JavaScript 엔진은 코드를 읽고 첫 번째 줄을 만나면 전역 메모리에 몇 가지 참조를 넣습니다.


전역 메모리 (또는 힙이라고도 함)는 JavaScript 엔진이 변수 및 함수 선언을 저장하는 영역입니다. 차이점은 사소한 것처럼 들릴 수 있지만 JavaScript의 함수 선언은 함수 호출과 다릅니다. 함수 선언은 함수가 받아 들여야 할 것과 호출 방법에 대한 설명입니다. 반면에 함수 호출은 이전에 선언 된 함수의 실제 실행입니다.


예를 들어, 엔진이 위의 코드를 읽을 때 전역 메모리에는 두 개의 바인딩이 채워집니다.


Global Memory in a JavaScript engine 


이 시점에서 아무것도 실행되지 않지만 다음과 같이 함수를 실행하려고 하면 어떻게 됩니까?


var num = 2;

function pow(num) {
    return num * num;
}

pow(num);


무슨 일이 일어날 것? 이제 상황이 재미있어집니다. 함수가 호출되면 JavaScript 엔진은 두 개의 상자를 더 넣을 공간을 만듭니다.


  • a Global Execution Context
  • a Call Stack

다음 섹션에서 그들이 무엇인지 봅시다.


글로벌 실행 컨텍스트 및 호출 스택 


JavaScript 엔진이 변수 및 함수 선언을 읽는 방법을 배웠습니다. 그것들은 전역 메모리 (힙)에 있게 됩니다. 그러나 이제 JavaScript 함수를 실행 했으며 엔진이 이를 처리해야 합니다. 어떻게? 모든 자바 스크립트 엔진에는 Call Stack이라는 기본 구성 요소가 있습니다. 콜 스택은 스택 데이터 구조입니다. 즉, 요소는 맨 위에서 입력 할 수 있지만 위에 요소가 있으면 떠날 수 없습니다. JavaScript 함수는 정확히 같습니다. 일단 실행되면 다른 함수가 계속 남아 있으면 호출 스택을 떠날 수 없습니다. 이 개념은 "자바 스크립트는 단일 스레드입니다"라는 문장으로 머리를 감싸는 데 도움이 되므로 주의 하십시오. 그러나 지금 우리의 예로 돌아가 봅시다. 함수가 호출되면 엔진은 해당 함수를 호출 스택 내에서 푸시합니다.


Call Stack and Global Memory in a JavaScript engine 


나는 콜 스택을 프링글스의 더미로 생각하는 것을 좋아합니다. 우리는 맨 위에서 모든 프링 글을 먼저 먹지 않고 더미의 맨 아래에서 프링 글을 먹을 수 없습니다! 운 좋게도 우리의 함수는 동기식입니다. 간단한 곱셈이고 빠르게 계산됩니다. 동시에 엔진은 JavaScript 코드가 실행되는 글로벌 환경 인 Global Execution Context도 할당합니다. 그 모습은 다음과 같습니다.


Call Stack, Global Memory, and Global Execution Context in a JavaScript engine 


물고기처럼 헤엄 치는 JavaScript 글로벌 함수가 바다로 Global Execution Context를 상상해보십시오. 멋집니다! 그러나 그것은 이야기의 절반에 지나지 않습니다. 함수에 중첩 된 변수나 하나 이상의 내부 함수가 있으면 어떻게 됩니까? 다음과 같은 간단한 변형에서도 JavaScript 엔진은 로컬 실행 컨텍스트를 만듭니다.


var num = 2;

function pow(num) {
    var fixed = 89;
    return num * num;
}

pow(num);


함수 pow 안에 fixed라는 변수를 추가했습니다. 이 경우 로컬 실행 컨텍스트에는 고정 된 상자가 포함됩니다. 다른 작은 상자 안에 작은 작은 상자를 그리는 것은 그리 좋지 않습니다! 지금 당신의 상상력을 사용하십시오. 로컬 실행 컨텍스트는 글로벌 실행 컨텍스트에 포함 된 녹색 상자 안에 pow 근처에 나타납니다. 또한 중첩 함수의 모든 중첩 함수에 대해 엔진이 더 많은 로컬 실행 컨텍스트를 생성한다고 상상할 수 있습니다. 이 상자는 너무 빨리 갈 수 있습니다! matrioska처럼! 이제 단일 스레드 스토리로 돌아가는 것은 어떻습니까? 그 의미는 무엇입니다 ?


함수를 처리하는 단일 호출 스택이 있기 때문에 JavaScript는 단일 스레드입니다. 즉, 실행 대기 중인 다른 함수가 있는 경우 함수는 콜 스택을 벗어날 수 없습니다. 동기 코드를 처리 할 때는 문제가 되지 않습니다. 예를 들어 두 숫자의 합은 동기식이며 마이크로 초 단위로 실행됩니다. 그러나 네트워크 통화와 외부 세계와의 다른 상호 작용은 어떻습니까? 운 좋게도 JavaScript 엔진은 기본적으로 비동기식으로 설계되었습니다. 그들이 한 번에 하나의 기능을 실행할 수 있더라도 외부 엔티티에 의해 느린 기능을 실행하는 방법이 있습니다 : 우리의 경우 브라우저. 나중에 이 주제를 살펴 보겠습니다.


그 동안 브라우저가 일부 JavaScript 코드를 로드 하면 엔진이 한 줄씩 읽고 다음 단계를 수행한다는 것을 알게 되었습니다.

  • 변수 및 함수 선언으로 전역 메모리 (힙)를 채 웁니다.
  • 모든 함수 호출을 호출 스택으로 푸시
  • 전역 함수가 실행되는 전역 실행 컨텍스트를 작성합니다.
  • 작은 로컬 실행 컨텍스트를 많이 생성합니다 (내부 변수 또는 중첩 함수가 있는 경우).

이제 모든 JavaScript 엔진의 기본에 동기 메커니즘의 큰 그림이 있어야 합니다. 다음 섹션에서는 JavaScript에서 비동기 코드가 작동하는 방식과 작동 방식을 살펴 봅니다.


비동기 JavaScript, 콜백 큐 및 이벤트 루프 


글로벌 메모리, 실행 컨텍스트 및 호출 스택은 브라우저에서 동기 JavaScript 코드가 실행되는 방식을 설명합니다. 그러나 우리는 무언가를 놓치고 있습니다. 실행할 비동기 함수가 있으면 어떻게 됩니까? 비동기 함수란 외부 세계와의 모든 상호 작용을 완료하는 데 약간의 시간이 걸릴 수 있음을 의미합니다. REST API 호출 또는 타이머 호출은 실행하는 데 몇 초가 걸릴 수 있으므로 비동기식입니다. 지금까지 엔진에 있는 요소를 사용하면 콜 스택 및 브라우저를 차단하지 않고 이러한 종류의 함수를 처리 할 수 있는 방법이 없습니다.


콜 스택은 한 번에 하나의 함수를 실행할 수 있으며 하나의 차단 함수조차도 문자 그대로 브라우저를 정지 시킬 수 있습니다. 운 좋게도 JavaScript 엔진은 영리하며 브라우저의 도움을 받아 항목을 정렬 할 수 있습니다. setTimeout과 같은 비동기 함수를 실행하면 브라우저가 해당 함수를 가져 와서 실행합니다. 다음과 같은 타이머를 고려하십시오.


setTimeout(callback, 10000);

function callback(){
    console.log('hello timer!');
}


setTimeout을 수백 번 보았지만 내장 JavaScript 함수가 아니라는 것을 모를 수도 있습니다. 즉, JavaScript가 탄생했을 때 언어에 내장 된 setTimeout이 없었습니다. 실제로 setTimeout은 브라우저가 무료로 제공하는 편리한 도구 모음 인 브라우저 API의 일부입니다. 얼마나 친절 해요! 실제로 무엇을 의미합니까? setTimeout은 브라우저에서 함수를 직접 실행하는 브라우저 API이므로 (콜 스택에 잠시 나타나지만 즉시 제거됩니다). 그런 다음 10 초 후에 브라우저는 전달한 콜백 함수를 가져와 콜백 큐로 옮깁니다.


이 시점에서 JavaScript 엔진 안에 두 개의 상자가 더 있습니다. 다음 코드를 고려하면 :


var num = 2;

function pow(num) {
    return num * num;
}

pow(num);

setTimeout(callback, 10000);

function callback(){
    console.log('hello timer!');
}


다음과 같이 그림을 완성 할 수 있습니다.


More boxes: Callback Queue and Browser APIs 


보시다시피 setTimeout은 브라우저 컨텍스트 내에서 실행됩니다. 10 초 후에 타이머가 트리거되고 콜백 함수를 실행할 준비가 되었습니다. 그러나 먼저 콜백 큐를 거쳐야 합니다. 콜백 큐는 큐 데이터 구조이며 이름에서 알 수 있듯이 정렬 된 함수 큐입니다. 모든 비동기 함수는 콜백 큐를 통과해야 콜 스택으로 푸시됩니다. 그러나 누가 그 함수를 추진합니까? Event Loop라는 다른 구성 요소가 있습니다.


이벤트 루프에는 현재 하나의 작업만 있습니다. 콜 스택이 비어 있는지 확인해야 합니다. 콜백 대기열에 일부 함수가 있고 콜 스택이 비어 있으면 콜백을 콜 스택으로 푸시해야 합니다. 완료되면 함수가 실행됩니다. 다음은 비동기 및 동기 코드를 처리하기 위한 JavaScript 엔진의 큰 그림입니다.


More boxes: Callback Queue, Browser APIs, Event Loop 


callback()을 실행할 준비가 되었다고 상상해보십시오. pow()가 끝나면 호출 스택이 비어 있고 이벤트 루프가 callback()을 푸시 합니다. 그게 전부입니다! 위의 그림을 이해하면 모든 JavaScript를 이해할 수 있습니다. 기억하십시오 : 브라우저 API, 콜백 큐 및 이벤트 루프는 비동기 JavaScript의 기둥입니다. 그리고 만약 당신이 멋진 비디오라면 필립 로버츠 (Philip Roberts)의 이벤트 루프가 무엇인지 지켜 보는 것이 좋습니다. 이벤트 루프에 대한 최고의 설명 중 하나입니다. 우리는 비동기 JavaScript로 끝나지 않았기 때문에 잠시 기다려주십시오. 다음 섹션에서는 ES6 Promises를 자세히 살펴 보겠습니다.


콜백 지옥과 ES6 약속(Promises) 


콜백 함수는 모든 곳에서 JavaScript로 제공됩니다. 동기 코드와 비동기 코드 모두에 사용됩니다. 예를 들어 map 메소드를 고려하십시오.


function mapper(element){
    return element * 2;
}

[1, 2, 3, 4, 5].map(mapper);


mapper는 맵 안에 전달 된 콜백 함수입니다. 위의 코드는 동기식입니다. 그러나 대신 간격을 고려하십시오.


function runMeEvery(){
    console.log('Ran!');
}

setInterval(runMeEvery, 5000);


이 코드는 비동기식이지만 setInterval 내에서 콜백 runMeEvery를 전달합니다. 콜백은 JavaScript에서 널리 보급되어 수년 동안 콜백 지옥이라는 문제가 발생했습니다. JavaScript의 콜백 지옥은 다른 콜백 안에 중첩 된 콜백 안에 콜백이 중첩되는 프로그래밍의 "스타일"을 나타냅니다. JavaScript 프로그래머의 비동기 적 특성으로 인해 수년에 걸쳐 이 함정에 빠졌습니다. 솔직히 말해서 나는 극단적인 콜백 피라미드에 빠지지 않습니다. 어쩌면 읽을 수 있는 코드를 소중히 생각하기 때문에 항상 그 원칙을 고수하려고 합니다. 콜백 지옥에 빠지면 함수가 너무 많이 하고 있다는 신호입니다.


여기서 콜백 지옥을 다루지 않을 것입니다. 궁금한 점이 있으면 callbackhell.com 웹 사이트가 있습니다.이 웹 사이트에는 callbackhell.com이 더 자세한 문제를 탐색하고 몇 가지 솔루션을 제공합니다. 우리가 지금 집중하고 싶은 것은 ES6 약속입니다. ES6 Promises는 두려운 콜백 지옥을 해결하기 위해 JavaScript 언어에 추가되었습니다. 그러나 어쨌든 약속은 무엇입니까?


JavaScript Promise는 향후 이벤트를 나타냅니다. 약속은 성공으로 끝날 수 있습니다. 전문 용어에서 우리는 그것이 해결되었다고 말합니다. 그러나 약속 오류가 발생하면 거부 된 상태라고 합니다. 약속에는 기본 상태도 있습니다. 모든 새로운 약속은 보류 상태에서 시작됩니다. 자신의 약속을 만들 수 있습니까? 예. 새 Promise를 만들려면 콜백 함수를 전달하여 Promise 생성자를 호출합니다. 콜백 함수는 두 가지 매개 변수를 사용할 수 있습니다 : resolve 및 reject. 5 초 안에 해결 될 새로운 약속을 만들어 봅시다 (브라우저 콘솔에서 예제를 시도 할 수 있습니다).


const myPromise = new Promise(function(resolve){
    setTimeout(function(){
        resolve()
    }, 5000)
});


보시다시피 resolve는 Promise를 성공시키기 위해 요구하는 함수입니다. 반면에 거부는 거부 된 약속을 만듭니다.


const myPromise = new Promise(function(resolve, reject){
    setTimeout(function(){
        reject()
    }, 5000)
});


첫 번째 예에서는 두 번째 매개 변수이므로 거부를 생략 할 수 있습니다. 그러나 거부를 사용하려는 경우 해결을 생략 할 수 없습니다. 다시 말해 다음 코드는 작동하지 않으며 해결 된 약속으로 끝납니다.


// Can't omit resolve !

const myPromise = new Promise(function(reject){
    setTimeout(function(){
        reject()
    }, 5000)
});


이제 약속은 그렇게 유용하지 않습니까? 이 예제는 사용자에게 아무것도 인쇄하지 않습니다. 믹스에 데이터를 추가합시다. 해결 된 약속과 거부 된 약속 모두 데이터를 반환 할 수 있습니다. 예를 들면 다음과 같습니다.


const myPromise = new Promise(function(resolve) {
  resolve([{ name: "Chris" }]);
});


그러나 여전히 데이터를 볼 수 없습니다. Promise에서 데이터를 추출하려면 then이라는 메소드를 연결해야 합니다. 실제 데이터를 수신하는 콜백 (아이러니!)이 필요합니다.


const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});

myPromise.then(function(data) {
    console.log(data);
});


JavaScript 개발자이자 다른 사람들의 코드 소비자로서 대부분 외부에서 약속과 상호 작용합니다. 대신 라이브러리 제작자는 다음과 같이 Promise 생성자 내에서 레거시 코드를 래핑 할 가능성이 높습니다.


const shinyNewUtil = new Promise(function(resolve, reject) {
  // do stuff and resolve
  // or reject
});


필요할 때 Promise.resolve()를 호출하여 Promise를 작성하고 해결할 수도 있습니다.


Promise.resolve({ msg: 'Resolve!'})
.then(msg => console.log(msg));


JavaScript Promise를 요약하면 미래에 발생하는 이벤트에 대한 책갈피입니다. 이벤트가 보류 상태에서 시작하여 성공 (해결, 이행) 또는 실패 (거부) 될 수 있습니다. Promise는 데이터를 반환 할 수 있으며 Promise에 연결하여 해당 데이터를 추출 할 수 있습니다. 다음 섹션에서는 약속에서 발생하는 오류를 처리하는 방법을 살펴 봅니다.


ES6 약속 및 오류 처리 


JavaScript의 오류 처리는 최소한 동기 코드에서는 항상 간단합니다. 다음 예를 고려하십시오.


function makeAnError() {
  throw Error("Sorry mate!");
}

try {
  makeAnError();
} catch (error) {
  console.log("Catching the error! " + error);
}


출력은 다음과 같습니다.


Catching the error! Error: Sorry mate!


예상대로 catch 블록에 오류가 발생했습니다. 이제 비동기 함수를 사용해 봅시다 :


function makeAnError() {
  throw Error("Sorry mate!");
}

try {
  setTimeout(makeAnError, 5000);
} catch (error) {
  console.log("Catching the error! " + error);
}


위의 코드는 setTimeout으로 인해 비동기식입니다. 실행하면 어떻게 됩니까?


throw Error("Sorry mate!");
  ^

Error: Sorry mate!
    at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9)


이번에는 출력이 다릅니다. 오류가 catch 블록을 통과하지 못했습니다. 스택에 자유롭게 전파 할 수 있었습니다. try / catch는 동기 코드에서만 작동하기 때문입니다. 궁금한 점이 있으면 Node.js의 오류 처리에 문제가 자세히 설명되어 있습니다. 다행히 약속과 함께 비동기 오류를 동기식으로 처리하는 방법이 있습니다. 이전 섹션에서 리콜을 거부하면 약속이 거부됩니다.


const myPromise = new Promise(function(resolve, reject) {
  reject('Errored, sorry!');
});


위의 경우 콜백을 사용하여 캐치 핸들러로 오류를 처리 할 수 ​​있습니다 (다시).


const myPromise = new Promise(function(resolve, reject) {
  reject('Errored, sorry!');
});

myPromise.catch(err => console.log(err));


Promise를 만들고 거부하기 위해 Promise.reject()를 호출 할 수도 있습니다.


Promise.reject({msg: 'Rejected!'}).catch(err => console.log(err));


요약 : 약속 핸들러가 거부 된 약속에 대해 캐치 핸들러가 실행되면 then 핸들러가 실행됩니다. 그러나 그것은 이야기의 끝이 아닙니다. 나중에 우리는 try / catch와 함께 async / await가 어떻게 잘 작동하는지 볼 것이다.


ES6 약속 결합 자 : Promise.all, Promise.allSettled, Promise.any 및 친구들 


약속은 혼자서는 안됩니다. Promise API는 Promises를 결합하기 위한 많은 메소드를 제공합니다. 가장 유용한 것 중 하나는 Promise.all입니다. Promise는 모든 약속을 취하고 단일 약속을 반환합니다. 문제는 배열의 약속이 거부되면 Promise.all이 거부한다는 것입니다.


Promise.race는 배열의 약속 중 하나가 결정되자마자 해결되거나 거부됩니다. 약속 중 하나가 거부하면 여전히 거부합니다.


최신 버전의 V8도 Promise.allSettled 및 Promise.any라는 두 가지 새로운 결합기를 구현할 예정입니다. Promise.any는 여전히 제안서의 초기 단계에 있습니다.이 문서를 작성할 당시에는 아직 지원되지 않습니다. 그러나 이론은 Promise.any는 Promise가 가득 찼는 지 여부를 알릴 수 있다는 것입니다. Promise.race와의 차이점은 Promise.any가 약속 중 하나가 거부 되더라도 거부하지 않는다는 것입니다.


어쨌든 이 둘 중 가장 흥미로운 것은 Promise.allSettled입니다. 여전히 약속의 배열을 취하지만 약속 중 하나가 거부하더라도 단락 되지 않습니다. 최종 거부에 관계없이 약속 배열이 모두 정산 되었는지 확인하려는 경우에 유용합니다. 그것을 Promise.all의 중심으로 생각하십시오.


ES6 약속 및 마이크로 태스크 대기열(Microtask Queue) 


이전 섹션에서 기억하면 모든 비동기 콜백 함수는 콜백으로 들어가기 전에 콜백 대기열에 있게 됩니다. 그러나 이제 다음 코드를 실행하십시오.


console.log("Start");

setTimeout(() => {
  console.log("Log me!");
}, 0);

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});

myPromise.then(data => console.log(data));

console.log("End");


결과물은 무엇입니까? 우리의 약속은 지체 되지 않으므로 Log me!와 함께 돌아와야 합니다. 그러나 우리는 얻을 것입니다 :


Start
End
[ { name: 'Chris' } ]
Log me!


Promise에 전달 된 콜백 함수는 다른 운명을 가지고 있기 때문입니다. 마이크로 태스크 대기열에 의해 처리됩니다. 그리고 흥미로운 점이 있습니다. Microtask Queue는 Callback Queue보다 우선합니다. 이벤트 루프가 Microtask 큐에서 콜 스택 콜백으로 푸시 될 준비가 된 새 콜백이 있는지 확인하는 경우 우선 순위가 있습니다. Microtask Queue는 ECMAScript 2015에 추가되었습니다.


비동기식 진화 : 약속(Promises)에서 비동기 / 대기(async/await)


JavaScript는 빠르게 움직이고 있으며 매년 언어를 지속적으로 개선하고 있습니다. 약속은 도착 지점으로 보였지만 ECMAScript 2017 (ES8)에서는 async / await라는 새로운 구문이 탄생했습니다. async / await는 우리가 구문 설탕이라고 부르는 문체 개선입니다. async / await는 어떤 방법으로도 JavaScript를 변경하지 않습니다 (JavaScript는 이전 버전의 브라우저와 호환되며 기존 코드를 중단해서는 안 됨). 약속에 따라 비동기 코드를 작성하는 새로운 방법 일 뿐입니다. 예를 들어 봅시다. 앞서 우리는 다음과 같이 약속을 저장합니다.


const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});

myPromise.then((data) => console.log(data))


이제 async / await를 사용하면 독자의 관점에서 동기적으로 보이는 방식으로 비동기 코드를 처리 할 수 ​​있습니다. 대신에 비동기로 표시된 함수 안에 Promise를 감싸고 결과를 기다릴 수 있습니다.


const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});

async function getData() {
  const data = await myPromise;
  console.log(data);
}

getData();


말이 되나요? 이제 재미있는 기능은 비동기 함수가 항상 약속을 반환하고 아무도 그렇게 하지 못하게 한다는 것입니다.


async function getData() {
  const data = await myPromise;
  return data;
}

getData().then(data => console.log(data));


그리고 오류는 어떻습니까? async / await가 제공하는 혜택 중 하나는 try / catch를 사용할 수 있는 기회입니다. 오류를 처리하기 위해 catch 핸들러를 사용하는 Promise를 다시 살펴 보겠습니다.


const myPromise = new Promise(function(resolve, reject) {
  reject('Errored, sorry!');
});

myPromise.catch(err => console.log(err));


비동기 함수를 사용하면 다음 코드로 리팩터링 할 수 있습니다.


async function getData() {
  try {
    const data = await myPromise;
    console.log(data);
    // or return the data with return data
  } catch (error) {
    console.log(error);
  }
}

getData();


그래도 모든 사람이 이 스타일로 판매되는 것은 아닙니다. try / catch는 코드에 노이즈를 줄 수 있습니다. try / catch를 사용하는 동안 지적해야 할 또 다른 단점이 있습니다. try 블록 내에서 오류를 발생 시키는 다음 코드를 고려하십시오.


async function getData() {
  try {
    if (true) {
      throw Error("Catch me if you can");
    }
  } catch (err) {
    console.log(err.message);
  }
}

getData()
  .then(() => console.log("I will run no matter what!"))
  .catch(() => console.log("Catching err"));


두 문자열 중 콘솔에 인쇄되는 것은 무엇입니까? try / catch는 동기 구조이지만 비동기 함수는 Promise를 생성합니다. 그들은 두 개의 기차처럼 두 개의 다른 트랙을 여행합니다. 그러나 그들은 결코 만나지 않을 것입니다! 즉, throw에 의해 발생한 오류는 getData()의 catch 핸들러를 트리거 하지 않습니다. 위의 코드를 실행하면 "가능한 경우 잡아라"와 "어떤 일이 있어도 달리겠습니다!"가 표시됩니다. 실제 세계에서는 throw 핸들러를 트리거 하기를 원하지 않습니다. 가능한 해결책 중 하나는 함수에서 Promise.reject()를 반환하는 것입니다.


async function getData() {
  try {
    if (true) {
      return Promise.reject("Catch me if you can");
    }
  } catch (err) {
    console.log(err.message);
  }
}


이제 오류가 예상대로 처리됩니다.

getData()
  .then(() => console.log("I will NOT run no matter what!"))
  .catch(() => console.log("Catching err"));

"Catching err" // output


그 외에도 async / await는 JavaScript에서 비동기 코드를 구성하는 가장 좋은 방법으로 보입니다. 우리는 오류 처리를 더 잘 제어하고 코드가 더 깨끗해 보입니다. 어쨌든 모든 JavaScript 코드를 async / await로 리팩토링 하지 않는 것이 좋습니다. 이들은 팀과 논의해야 할 선택입니다. 그러나 당신이 평범한 약속을 사용하든 async / await를 사용하든 혼자 일한다면 그것은 개인적인 취향의 문제입니다.


결론


JavaScript는 웹 스크립팅 언어이며 먼저 컴파일 한 다음 엔진에서 해석하는 특성이 있습니다. 가장 많이 사용되는 JavaScript 엔진 중에는 Chrome과 Node.js에서 사용하는 V8과 웹 브라우저 Firefox 용으로 작성된 SpiderMonkey 및 Safari에서 사용되는 JavaScriptCore가 있습니다.


JavaScript 엔진에는 호출 스택, 전역 메모리, 이벤트 루프, 콜백 대기열과 같은 많은 부분이 있습니다. 이 모든 부분은 JavaScript에서 동기식 코드와 비동기식 코드를 처리하기 위한 완벽한 조정으로 함께 작동합니다. JavaScript 엔진은 단일 스레드이므로 함수 실행을 위한 단일 호출 스택이 있습니다. 이 제한은 JavaScript의 비동기 특성에 기초합니다. 시간이 필요한 모든 작업은 외부 엔터티 (예 : 브라우저) 또는 콜백 함수에서 담당해야 합니다.


ES6부터 JavaScript 엔진은 Microtask Queue도 구현합니다. Microtask 큐는 콜백 큐와 매우 유사한 큐 데이터 구조입니다. 차이점은 Microtask Queue가 ES6 약속에 의해 트리거 된 모든 콜백을 받는다는 것입니다. 실행기라고도 하는 이러한 콜백은 콜백 대기열이 처리하는 콜백보다 우선합니다. Jake Archibald는 작업, 마이크로 태스크, 대기열 및 일정에 정비사를 보다 자세하게 공개합니다.


ECMAScript 2015는 비동기 코드 흐름을 단순화하기 위해 약속했습니다. Promise는 비동기 객체이며 비동기 작업의 실패 또는 성공을 나타내는 데 사용됩니다. 그러나 개선은 거기서 멈추지 않았습니다. 2017 년에 async / await가 탄생했습니다. 비동기식 코드를 동기식으로 작성할 수 있게 하는 약속을 위한 문체입니다. 그리고 더 중요한 것은 try / catch를 사용하여 비동기 코드에서 오류를 처리 할 수 있게 하는 것입니다.



페이지 정보

조회 50회 ]  작성일19-10-17 17:28

웹학교