함수 내에서 변수를 수정 한 후에도 변수가 변경되지 않는 이유는 무엇입니까? -비동기 코드 참조

May 15 2014

다음 예 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이 모든 예제에서 출력 되는 이유는 무엇 입니까? 해결 방법을 원하지 않고 이런 일이 발생 하는지 알고 싶습니다 .


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

답변

605 FabrícioMatté May 15 2014 at 06:55

한마디 대답 : 비동기 성 .

머리말

이 주제는 여기 Stack Overflow에서 최소한 수천 번 반복되었습니다. 따라서 먼저 매우 유용한 리소스를 지적하고 싶습니다.


당면한 질문에 대한 답

먼저 일반적인 행동을 추적 해 보겠습니다. 모든 예에서는 함수outerScopeVar 내에서 수정 됩니다 . 이 함수는 분명히 즉시 실행되지 않으며 인수로 할당되거나 전달됩니다. 이것이 우리가 콜백 이라고 부르는 것 입니다.

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

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

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

모든 경우에 향후 언젠가 실행될 수있는 콜백이 있습니다 . 이 "미래에있을 때"를 비동기식 흐름이라고 합니다.

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

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

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

간단히 말해, 콜백 함수는 동 기적으로 생성되지만 비동기 적으로 실행됩니다. 비동기 함수가 실행되었다는 것을 알기 전까지는 비동기 함수의 실행에 의존 할 수 없으며 어떻게해야합니까?

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

고유 한 콜백 로직 구현

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

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

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

참고 :setTimeout 일반 비동기 함수로 임의 지연을 사용 하고 있는데 동일한 예제가 Ajax 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로 콜백 지옥 을 막을 수있는 방법이 있지만 , promise는 인기를 얻고 있으며 현재 ES6에서 표준화되고 있습니다 ( Promise-MDN 참조 ).

Promises (일명 Futures)는 비동기 코드를보다 선형적이고 따라서 즐겁게 읽을 수 있지만 전체 기능을 설명하는 것은이 질문의 범위를 벗어납니다. 대신 관심있는 분들을 위해 다음과 같은 훌륭한 리소스를 남겨 두겠습니다.


JavaScript 비동기성에 대한 더 많은 읽기 자료


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

이 질문을 Ajax와 관련이없는 비동기 성 문제에 답하기 위해 표준 주제로 바꾸고 싶습니다 ( AJAX 호출에서 응답을 반환하는 방법이 있습니까? ). 따라서이 주제는 가능한 한 훌륭하고 도움이되도록 귀하의 도움이 필요합니다. !

158 Matt May 29 2014 at 16:09

Fabrício의 대답은 확실합니다. 그러나 나는 그의 대답을 덜 기술적 인 것으로 보완하고 싶었습니다 . 비동기 성 개념을 설명하는 데 도움이되는 비유에 중점을 둡니다 .


비유 ...

어제 제가하고 있던 작업에는 동료로부터 정보가 필요했습니다. 나는 그를 울렸다. 대화가 진행된 방법은 다음과 같습니다.

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

: 물론 이죠. 30 분 정도 걸릴까요?

: 대단한 밥이다. 정보를 받으면 다시 전화주세요!

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


대화가 대신 이렇게되었다고 상상해보십시오.

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

: 물론 이죠. 30 분 정도 걸릴까요?

: 대단한 밥이다. 기다릴게.

그리고 나는 거기 앉아서 기다렸다. 그리고 기다렸다. 그리고 기다렸다. 40 분 동안. 아무것도하지 않고 기다리고 있습니다. 결국 Bob이 정보를 제공하고 전화를 끊고 보고서를 작성했습니다. 하지만 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);

위의 코드에서 우리는 자바 스크립트 가을 로드하도록 요청 lolcat.png하고 있는데 이것은 느린 작업입니다. 이 느린 작업이 완료되면 콜백 함수가 실행되지만 그 동안 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';

JavaScript에서 일부 코드를 정의하는 유일한 방법이지만 나중에까지 실행하지 않기 때문에 항상 콜백이 함수로 지정되는 것을 볼 수 있습니다.

따라서 모든 예제 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) 를 제거 할 수있었습니다 .

75 JohnnyHK Jan 21 2015 at 06:42

다음은 promise 및 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);
});

약속

Promise는 여러 비동기 작업을 조정하기 위해 자연스럽게 결합 될 수 있기 때문에 콜백보다 더 유연한 대안을 제공합니다. 약속 / 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는 Deferreds와 함께 promise와 유사한 기능을 제공합니다.

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

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

비동기 / 대기

JavaScript 환경에 asyncawait(예 : Node.js 7.6 이상) 지원이 포함 된 경우 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();
58 JohannesFahrenkrug Dec 08 2015 at 23:48

명백한 것을 말하면 컵은 outerScopeVar.

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

14 Teja Feb 26 2016 at 10:59

다른 답변은 훌륭하며 이에 대한 직접적인 답변을 제공하고 싶습니다. jQuery 비동기 호출로 제한

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

당신의 모범을 고려

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

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

게시 요청을 완료하는 데 10 초가 걸린다고 가정 해 보겠습니다. 값은 outerScopeVar해당 10 초 후에 만 ​​설정됩니다.

시도하려면

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);
});

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

11 TomSebastian Oct 27 2015 at 13:35

이러한 모든 시나리오 outerScopeVar에서 값이 비동기 적으로 수정되거나 할당 되거나 나중에 발생 (일부 이벤트가 발생하기를 기다리거나 수신 대기)하므로 현재 실행이 대기하지 않습니다 .outerScopeVar = undefined

각 예를 논의 해 보겠습니다 (일부 이벤트가 발생하기 위해 비동기 적으로 호출되거나 지연되는 부분을 표시했습니다).

1.

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

2.

여기서 timeout 이벤트는 지정된 시간 후에 핸들러를 호출하는 역할을합니다. 여기 0에 있지만 여전히 비동기 이벤트를 등록하여 Event Queue실행 을 위해 마지막 위치에 추가되어 지연이 보장됩니다.

삼.

이번에는 아약스 콜백입니다.

4.

Node는 비동기 코딩의 왕으로 간주 할 수 있으며, 표시된 함수는 지정된 파일을 읽은 후 실행되는 콜백 핸들러로 등록됩니다.

5.

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

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