[javascript] 함수 내에서 변수를 수정 한 후 왜 변수가 변경되지 않습니까? -비동기 코드 참조

다음 예제를 보면 왜 outerScopeVar모든 경우에 정의되지 않습니까?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);

// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// with promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

undefined이 모든 예제에서 왜 출력 됩니까? 해결 방법을 원하지 않습니다 . 이런 일이 발생 하는지 알고 싶습니다 .


참고 : 이것은 JavaScript 비동기 성에 대한 표준 질문입니다 . 이 질문을 개선하고 커뮤니티가 식별 할 수있는 더 간단한 예제를 추가하십시오.



답변

한마디로 대답 : 비동기 성 .

서문

이 항목은 여기에서 스택 오버플로에서 적어도 수천 번 반복되었습니다. 따라서 먼저 매우 유용한 리소스를 지적하고 싶습니다.


당면한 질문에 대한 답

먼저 일반적인 동작을 추적 해 봅시다. 모든 예제에서는 함수outerScopeVar 내부에서 수정 됩니다 . 이 기능은 즉시 실행되지 않으며 인수로 지정되거나 전달됩니다. 이것이 우리가 콜백 이라고 부르는 것 입니다.

이제 문제는 언제 콜백이 호출됩니까?

경우에 따라 다릅니다. 몇 가지 일반적인 동작을 다시 추적 해 보겠습니다.

  • img.onload이미지가 성공적으로로드 된 경우 나중에 호출 될 수 있습니다 .
  • setTimeout지연 시간이 만료되고에 의해 시간 초과가 취소되지 않은 후 나중에 언젠가 호출 될 수 있습니다 clearTimeout. 참고 : 0지연으로 사용하더라도 모든 브라우저에는 최소 시간 초과 지연 제한 (HTML5 사양에서 4ms로 지정)이 있습니다.
  • jQuery $.post의 콜백은 나중에 Ajax 요청이 성공적으로 완료되었을 때 호출 될 수 있습니다 .
  • Node.js 는 파일이 성공적으로 읽히거나 오류가 발생했을 때 언젠가fs.readFile 호출 될 수 있습니다 .

모든 경우에 향후 콜백이 발생할 수 있습니다 . 이 “언젠가”는 비동기 흐름이라고 합니다.

비동기식 실행이 동기식 흐름에서 빠져 나옵니다. 즉, 비동기 코드 스택이 실행되는 동안 비동기 코드는 실행 되지 않습니다 . 이것이 자바 스크립트가 단일 스레드라는 의미입니다.

보다 구체적으로, (a) 동기 코드 스택을 실행하지 않고 JS 엔진이 유휴 상태 인 경우 비동기 콜백 (예 : 만료 된 시간 초과, 수신 된 네트워크 응답)을 트리거 한 이벤트를 폴링하고 차례로 실행합니다. 이를 이벤트 루프 로 간주 합니다 .

즉, 손으로 그린 ​​빨간색 모양으로 강조 표시된 비동기 코드는 해당 코드 블록의 나머지 모든 동기 코드가 실행 된 후에 만 ​​실행될 수 있습니다.

강조 표시된 비동기 코드

요컨대, 콜백 함수는 동 기적으로 생성되지만 비동기 적으로 실행됩니다. 비동기 함수가 실행되었다는 것을 알기 전까지는 비동기 함수의 실행에 의존 할 수 없습니다.

정말 간단합니다. 비동기 함수 실행에 의존하는 논리는이 비동기 함수 내부에서 시작 / 호출되어야합니다. 예를 들어, 콜백 함수 내 에서 alerts 및 console.logs를 너무 이동하면 결과가 해당 시점에 사용 가능하므로 예상 결과가 출력됩니다.

자신의 콜백 로직 구현

비동기 함수의 결과로 더 많은 작업을 수행하거나 비동기 함수가 호출 된 위치에 따라 결과로 다른 작업을 수행해야하는 경우가 종종 있습니다. 좀 더 복잡한 예를 다루겠습니다.

var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);

function helloCatAsync() {
    setTimeout(function() {
        outerScopeVar = 'Nya';
    }, Math.random() * 2000);
}

참고 : 내가 사용 setTimeout하는 일반 비동기 함수로 임의의 지연, 같은 예는 아약스, 적용 readFile, onload및 기타 비동기 흐름.

이 예는 다른 예와 동일한 문제를 분명히 겪고 있으며 비동기 함수가 실행될 때까지 기다리지 않습니다.

우리 자신의 콜백 시스템을 구현하는 방법을 다루겠습니다. 우선, 우리 outerScopeVar는이 경우 완전히 쓸모없는 그 추한 것을 제거 합니다. 그런 다음 함수 인수 인 콜백을 허용하는 매개 변수를 추가합니다. 비동기 작업이 완료되면이 콜백을 호출하여 결과를 전달합니다. 구현 (주석을 순서대로 읽으십시오) :

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    alert(result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    // 3. Start async operation:
    setTimeout(function() {
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

위 예제의 코드 스 니펫 :

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
console.log("1. function called...")
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    console.log("5. result is: ", result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    console.log("2. callback here is the function passed as argument above...")
    // 3. Start async operation:
    setTimeout(function() {
    console.log("3. start async operation...")
    console.log("4. finished async operation, calling the callback, passing the result...")
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

대부분의 실제 사례에서 DOM API와 대부분의 라이브러리는 이미 콜백 기능을 제공합니다 ( helloCatAsync이 예제에서는 구현). 콜백 함수를 전달하고 동기 흐름에서 실행될 것임을 이해하고이를 수용하도록 코드를 재구성해야합니다.

또한 비동기 특성으로 인해 return비동기 코드가 이미 실행 완료된 후에도 비동기 콜백이 실행되므로 비동기 플로우에서 콜백이 정의 된 동기 플로우로 값을 되돌릴 수 없습니다.

대신에 return비동기 콜백에서 값을 보내고, 당신은 … 약속을 콜백 패턴을 사용하기 위해, 또는 것입니다.

약속

콜백 지옥 을 바닐라 JS와 함께 유지하는 방법이 있지만 약속은 점점 커지고 있으며 현재 ES6에서 표준화되고 있습니다 ( Promise-MDN 참조 ).

약속 (일명 Futures)은 비동기 코드를보다 선형적이고 유쾌하게 읽을 수있게하지만 전체 기능을 설명하는 것은이 질문의 범위를 벗어납니다. 대신, 나는 다음과 같은 훌륭한 자료를 관심의 대상으로 남길 것이다.


JavaScript 비동기성에 대한 추가 자료

  • Art of Node-Callbacks 는 바닐라 JS 예제와 Node.js 코드로 비동기 코드와 콜백에 대해 잘 설명합니다.

참고 : 이 답변을 Community Wiki로 표시 했으므로 평판이 100 이상인 사람은 누구나 편집하고 개선 할 수 있습니다! 이 답변을 개선하거나 원할 경우 완전히 새로운 답변을 제출하십시오.

이 질문을 Ajax와 관련이없는 비 동시성 문제에 답변하기 위해 정식 주제로 바꾸고 싶습니다 ( AJAX 호출에서 응답을 반환하는 방법무엇입니까? ) !


답변

Fabrício의 대답은 바로 그 자리에 있습니다. 그러나 나는 그의 대답을 덜 기술적 인 것으로 보완하고 싶었다. 이것은 비 동시성의 개념을 설명하기 위해 유추에 초점을 둔다 .


유추 …

어제, 내가하고있는 일에는 동료의 정보가 필요했습니다. 나는 그를 울렸다. 대화 방법은 다음과 같습니다.

: 안녕하세요 밥, 나는 우리가 어떻게 알 필요가 foo는 ‘D를 ‘지난 주 거라고. Jim은 이에 대한 보고서를 원하며 귀하는 이에 대한 세부 사항을 아는 유일한 사람입니다.

Bob : 물론이지만 30 분 정도 걸릴까요?

: 잘 했어. 정보를 받으면 다시 연락주세요!

이 시점에서 나는 전화를 끊었다. 보고서를 완성하기 위해 Bob의 정보가 필요했기 때문에 보고서를 남기고 커피를 마시 러 갔다가 이메일을 받았습니다. 40 분 후 (밥이 느리다) 밥이 전화를 걸어 필요한 정보를 주었다. 이 시점에서 필요한 모든 정보를 얻었으므로 보고서 작업을 다시 시작했습니다.


대화가 대신 이런 식으로 진행되었다고 상상해보십시오.

: 안녕하세요 밥, 나는 우리가 어떻게 알 필요가 foo는 ‘D를 ‘지난 주 거라고. Jim은 이에 대한 보고서를 원하며 이에 대한 세부 정보를 알고있는 유일한 사람입니다.

Bob : 물론이지만 30 분 정도 걸릴까요?

: 잘 했어. 기다릴게.

그리고 나는 거기에 앉아 기다렸다. 그리고 기다렸다. 그리고 기다렸다. 40 분 동안. 기다리는 것 외에는 아무것도하지 않습니다. 결국 밥은 나에게 정보를 주었고, 우리는 끊었고 보고서를 완성했다. 그러나 나는 40 분의 생산성을 잃었습니다.


이것은 비동기 대 동기 동작입니다.

이것이 바로 우리 질문의 모든 예에서 일어나는 일입니다. 이미지 로딩, 디스크에서 파일 로딩 및 AJAX를 통한 페이지 요청은 모두 느리게 작동합니다 (현대 컴퓨팅의 맥락에서).

JavaScript는 이러한 느린 작업이 완료 되기를 기다리지 않고 느린 작업이 완료 될 때 실행될 콜백 함수를 등록 할 수 있도록합니다. 그러나 그 동안 JavaScript는 다른 코드를 계속 실행합니다. 느린 작업이 완료되기를 기다리는 동안 JavaScript가 다른 코드 를 실행한다는 사실 은 동작을 비동기 적으로 만듭니다 . JavaScript가 다른 코드를 실행하기 전에 작업이 완료 될 때까지 기다렸다면 이는 동기 동작 일 것입니다.

var outerScopeVar;
var img = document.createElement('img');

// Here we register the callback function.
img.onload = function() {
    // Code within this function will be executed once the image has loaded.
    outerScopeVar = this.width;
};

// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);

위의 코드에서 JavaScript를로드하도록 요청 lolcat.png하고 있습니다. 이는 sloooow 작업입니다. 이 느린 작업이 완료되면 콜백 함수가 실행되지만 그 동안 JavaScript는 다음 코드 줄을 계속 처리합니다. 즉 alert(outerScopeVar).

이것이 경고가 표시되는 이유입니다 undefined. (가) 이후 alert()즉시 처리보다는 이미지를로드 한 후.

코드를 수정하려면 alert(outerScopeVar)코드 콜백 함수 옮기면 됩니다. 결과적으로 더 이상 outerScopeVar전역 변수로 선언 된 변수 가 필요하지 않습니다 .

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

당신은 것입니다 항상 그 몇 가지 코드를 정의하는 자바 스크립트에서 유일하게 * 방법이기 때문에, 콜백은 함수로 지정되어 볼 수 있지만, 나중에까지 그것을 실행할 수 없습니다.

따라서 모든 예제 function() { /* Do something */ }에서 콜백은 콜백입니다. 모든 예제 를 수정 하려면 작업의 응답이 필요한 코드를 여기로 옮기면됩니다!

* 기술적 eval()으로도 사용할 수 있지만 이 목적 eval()에는 악의적 입니다


발신자를 대기 상태로 유지하려면 어떻게합니까?

현재 이와 비슷한 코드가있을 수 있습니다.

function getWidthOfImage(src) {
    var outerScopeVar;

    var img = document.createElement('img');
    img.onload = function() {
        outerScopeVar = this.width;
    };
    img.src = src;
    return outerScopeVar;
}

var width = getWidthOfImage('lolcat.png');
alert(width);

그러나 지금 우리는 그 return outerScopeVar일이 즉시 일어난다 는 것을 알고 있습니다 . onload콜백 함수가 변수를 업데이트 하기 전에 이 리드 getWidthOfImage()반환 undefined, 및 undefined경고되고.

이 문제를 해결하려면 함수 호출 getWidthOfImage()이 콜백을 등록한 다음 해당 콜백 내에 있도록 너비의 경고를 이동해야합니다.

function getWidthOfImage(src, cb) {
    var img = document.createElement('img');
    img.onload = function() {
        cb(this.width);
    };
    img.src = src;
}

getWidthOfImage('lolcat.png', function (width) {
    alert(width);
});

… 전과 같이 전역 변수 (이 경우 width) 를 제거 할 수있었습니다 .


답변

다음은 약속 및 async / await를 사용하는 몇 가지 예뿐만 아니라 빠른 참조를 원하는 사람들에게 더 간결한 답변입니다.

비동기 메소드 (이 경우 setTimeout) 를 호출 하고 메시지를 리턴하는 함수에 대해 순진한 접근 방식 (작동하지 않음)으로 시작하십시오 .

function getMessage() {
  var outerScopeVar;
  setTimeout(function() {
    outerScopeVar = 'Hello asynchronous world!';
  }, 0);
  return outerScopeVar;
}
console.log(getMessage());

undefined콜백이 호출되고 업데이트 getMessage되기 전에 반환 setTimeout되므로이 경우에 로그 됩니다 outerScopeVar.

이를 해결하는 두 가지 주요 방법은 콜백약속을 사용하는 것입니다 .

콜백

여기서 변경 사항은 사용 가능한 경우 호출 코드로 결과를 다시 전달하기 위해 호출 getMessage되는 callback매개 변수 를 허용 한다는 것입니다.

function getMessage(callback) {
  setTimeout(function() {
    callback('Hello asynchronous world!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

약속

약속은 여러 비동기 작업을 조정하기 위해 자연스럽게 결합 될 수 있기 때문에 콜백보다 유연한 대안을 제공합니다. 약속 / A + 표준 구현은 기본적으로 Node.js를 (0.12+) 많은 현재 브라우저에서 제공뿐만 아니라 같은 라이브러리에서 구현 블루 버드Q .

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hello asynchronous world!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);
});

jQuery 연기

jQuery는 지연된 약속과 유사한 기능을 제공합니다.

function getMessage() {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hello asynchronous world!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);
});

비동기 / 기다리기

JavaScript 환경에 Node.js 7.6 이상과 같은 async및 지원이 포함 된 경우 함수 await내에서 약속을 동 기적으로 사용할 수 있습니다 async.

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hello asynchronous world!');
        }, 0);
    });
}

async function main() {
    let message = await getMessage();
    console.log(message);
}

main();


답변

명백하게 말하면 컵은을 나타냅니다 outerScopeVar.

비동기 함수는 다음과 같습니다.

커피에 대한 비동기식 호출


답변

다른 답변은 훌륭하며 이에 대한 간단한 답변을 제공하고자합니다. jQuery 비동기 호출로만 제한

모든 ajax 호출 ( $.get또는 $.postor 포함 $.ajax)은 비동기식입니다.

당신의 모범을 고려

var outerScopeVar;  //line 1
$.post('loldog', function(response) {  //line 2
    outerScopeVar = response;
});
alert(outerScopeVar);  //line 3

코드 실행은 라인 1에서 시작하여 변수를 선언하고 라인 2에서 트리거 및 비동기 호출 (즉, 사후 요청)을 실행하며 사후 요청이 실행을 완료 할 때까지 기다리지 않고 라인 3에서 실행을 계속합니다.

게시 요청을 완료하는 데 10 초가 걸리고 10 초 outerScopeVar후에 만 값 이 설정됩니다.

시험해보기 위해

var outerScopeVar; //line 1
$.post('loldog', function(response) {  //line 2, takes 10 seconds to complete
    outerScopeVar = response;
});
alert("Lets wait for some time here! Waiting is fun");  //line 3
alert(outerScopeVar);  //line 4

이제 이것을 실행할 때 라인 3에 경고가 표시됩니다. 이제 포스트 요청이 어떤 값을 리턴 할 때까지 잠시 기다리십시오. 그런 다음 확인을 클릭하면 경고 상자에서 기다렸다가 다음 경고에 예상 값이 인쇄됩니다.

실제 시나리오에서 코드는

var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
    alert(outerScopeVar);
});

비동기 호출에 의존하는 모든 코드는 비동기 블록 내부로 이동하거나 비동기 호출을 대기하여 이동합니다.


답변

이 모든 시나리오 에서 현재 실행이 대기하지 않는 비동기식 또는 나중에 발생하는 이벤트 (일부 이벤트가 발생하거나 수신 대기 중)에outerScopeVar 값이 수정되거나 할당 됩니다.outerScopeVar = undefined

각 예제에 대해 논의 해 봅시다 (일부 이벤트가 발생하기 위해 비동기 또는 지연이라고하는 부분을 표시했습니다).

1.

여기에 이미지 설명을 입력하십시오

여기에 특정 이벤트에 대해 실행될 이벤트 목록을 등록합니다. 여기에 이미지를로드합니다. 그런 다음 현재 행이 다음 줄 img.src = 'lolcat.png';과 연속되어 alert(outerScopeVar);이벤트가 발생하지 않을 수 있습니다. 즉, funtion img.onload은 참조 된 이미지가 비동기 적으로로드되기를 기다립니다. 이것은 다음의 모든 예에서 발생하며 이벤트는 다를 수 있습니다.

2.

2

여기서 타임 아웃 이벤트가 역할을 수행하며 지정된 시간 이후에 핸들러를 호출합니다. 여기 0에 있지만 여전히 Event Queue실행 을 위해 마지막 위치에 추가 될 비동기 이벤트를 등록 하여 보장 된 지연을 만듭니다.

삼.

여기에 이미지 설명을 입력하십시오
이번에는 아약스 콜백입니다.

4.

여기에 이미지 설명을 입력하십시오

노드는 비동기 코딩의 왕으로 간주 될 수 있습니다. 여기서 표시된 함수는 지정된 파일을 읽은 후 실행될 콜백 핸들러로 등록됩니다.

5.

여기에 이미지 설명을 입력하십시오

명백한 약속 (무언가 미래에 이루어질 것)은 비동기 적입니다. JavaScript에서 Deferred, Promise 및 Future의 차이점무엇입니까?를 참조하십시오 .

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript


답변