→ 다른 예제를 사용한 비동기 동작에 대한보다 일반적인 설명은 함수 내에서 변수를 수정 한 후 왜 변수가 변경되지 않습니까? 를 참조하십시오 . -비동기 코드 참조
→ 문제를 이미 이해 한 경우 아래 가능한 해결 방법으로 건너 뛰십시오.
문제
Ajax 의 A 는 비동기를 나타냅니다 . 즉, 요청 전송 (또는 응답 수신)은 정상적인 실행 흐름에서 제외됩니다. 귀하의 예에서 즉시 반환하고 다음 명령문은 다음과 같이 전달 된 함수 전에 실행됩니다.$.ajax
return result;
success
콜백으로 호출 됩니다.
다음은 동기 흐름과 비동기 흐름의 차이를 더 명확하게 만드는 유추입니다.
동기식
친구에게 전화를 걸어 무언가를 찾아 보라고 상상해보십시오. 시간이 걸릴 수 있지만 친구가 필요한 답변을 줄 때까지 전화를 기다리고 우주를 응시합니다.
"정상"코드를 포함하는 함수를 호출 할 때도 마찬가지입니다.
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
findItem
실행하는 데 시간이 오래 걸릴 수 있지만 이후에 오는 모든 코드 var item = findItem();
는 기다려야합니다 는 함수가 결과를 반환 까지 합니다.
비동기
같은 이유로 친구에게 다시 전화하십시오. 그러나 이번에는 당신이 그에게 당신이 서두르고 있다고 말하면 휴대 전화로 다시 전화 해야합니다 . 당신은 전화를 끊고 집을 떠나고 당신이 계획 한 일을합니다. 친구가 전화를하면 친구에게 준 정보를 처리하게됩니다.
그것이 바로 Ajax 요청을 할 때 일어나는 일입니다.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
응답을 기다리는 대신 실행이 즉시 계속되고 Ajax 호출 후 명령문이 실행됩니다. 응답을 받으려면 응답을 받으면 호출 할 함수, 콜백 (알림? 콜백) 을 제공합니다. ?)을 제공합니다. 콜백이 호출되기 전에 해당 호출 다음에 오는 명령문이 실행됩니다.
해결책
JavaScript의 비동기 특성을 수용하십시오!특정 비동기 작업은 동기 상대방을 제공하지만 ( "Ajax"도 마찬가지) 일반적으로 브라우저 컨텍스트에서 사용하는 것은 바람직하지 않습니다.
왜 나쁜가요?
JavaScript는 브라우저의 UI 스레드에서 실행되며 장기 실행 프로세스는 UI를 잠그므로 응답하지 않습니다. 또한 JavaScript의 실행 시간에는 상한이 있으며 브라우저는 사용자에게 계속 실행 여부를 묻습니다.
이 모든 것은 정말 나쁜 사용자 경험입니다. 사용자는 모든 것이 잘 작동하고 있는지 알 수 없습니다. 또한 연결 속도가 느린 사용자에게는 효과가 더 나빠질 수 있습니다.
다음에서 우리는 서로 위에 세 가지 다른 솔루션을 살펴볼 것입니다.
- 다음과 같은 약속
async/await
(ES2017 +, 트랜스 파일러 또는 재생기를 사용하는 경우 이전 브라우저에서 사용 가능)
- 콜백 (인기 노드)
- 와 약속
then()
(ES2015 +, 당신은 많은 약속 라이브러리 중 하나를 사용하는 경우 이전 버전의 브라우저에서 사용 가능)
세 가지 모두 현재 브라우저 및 노드 7 이상에서 사용할 수 있습니다.
2017 년에 출시 된 ECMAScript 버전 에는 비동기 함수에 대한 구문 수준의 지원이 도입되었습니다 . async
및 의 도움으로 await
"동기식 스타일"로 비동기식을 작성할 수 있습니다. 코드는 여전히 비동기 적이지만 읽기 / 이해하기가 더 쉽습니다.
async/await
약속 위에 구축 : async
함수는 항상 약속을 반환합니다. await
약속을 "포장 해제"하고 약속이 해결 된 값을 얻거나 약속이 거부되면 오류가 발생합니다.
중요 : 당신은 사용할 수있는 await
내부 async
기능. 현재 최상위 레벨 await
은 아직 지원되지 않으므로 컨텍스트 를 시작하려면 비동기 IIFE ( 즉시 호출 된 함수 표현식 )를 작성해야합니다 async
.
당신은에 대해 자세히 읽을 수 async
및 await
MDN에 있습니다.
다음은 위의 지연을 기반으로 구축 된 예입니다.
// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
현재 브라우저 및 노드 버전이 지원 async/await
됩니다. 또한 재생기 (또는 Babel 과 같은 재생기를 사용하는 도구)를 사용 하여 코드를 ES5로 변환하여 이전 환경을 지원할 수 있습니다 .
함수가 콜백을 받도록 허용
콜백은 단순히 다른 함수로 전달되는 함수입니다. 다른 함수는 준비가 될 때마다 전달 된 함수를 호출 할 수 있습니다. 비동기 프로세스와 관련하여 콜백은 비동기 프로세스가 완료 될 때마다 호출됩니다. 일반적으로 결과는 콜백으로 전달됩니다.
질문의 예 foo
에서 콜백 을 수락하고 콜백으로 사용할 수 있습니다 success
. 그래서 이건
var result = foo();
// Code that depends on 'result'
된다
foo(function(result) {
// Code that depends on 'result'
});
여기에 함수 "인라인"을 정의했지만 모든 함수 참조를 전달할 수 있습니다.
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
자체는 다음과 같이 정의됩니다.
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
foo
우리가 호출 할 때 전달하는 함수를 가리키고 단순히 전달할 수 있습니다 success
. 즉, Ajax 요청이 성공 하면 콜백에 응답을 $.ajax
호출 callback
하고 전달합니다.result
을 정의한 방식이므로 ).
콜백에 전달하기 전에 응답을 처리 할 수도 있습니다.
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
콜백을 사용하여 코드를 작성하는 것이 생각보다 쉽습니다. 결국, 브라우저의 JavaScript는 많은 이벤트 중심 (DOM 이벤트)입니다. Ajax 응답을받는 것은 이벤트 일뿐입니다.
타사 코드로 작업해야 할 경우 문제가 발생할 수 있지만 대부분의 문제는 응용 프로그램 흐름을 생각하면 해결 될 수 있습니다.
약속 API는 ECMAScript를 6 (ES2015)의 새로운 기능이지만, 좋은이 브라우저 지원을 이미. 표준 Promises API를 구현하고 비동기 함수 (예 : 블루 버드 ) 의 사용 및 구성을 용이하게하는 추가 메소드를 제공하는 많은 라이브러리도 있습니다 .
약속은 미래 가치를 위한 컨테이너입니다 . 약속은 값을 받거나 ( 해결됨 ) 취소 ( 거부 ) 될 때이 값에 액세스하려는 모든 "리스너"에게 알립니다.
일반 콜백에 비해 장점은 코드를 분리 할 수 있고 작성이 더 쉽다는 것입니다.
다음은 약속을 사용하는 간단한 예입니다.
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
delay()
.then(function(v) { // `delay` returns a promise
console.log(v); // Log the value once it is resolved
})
.catch(function(v) {
// Or do something else if it is rejected
// (it would not happen in this example, since `reject` is not called).
});
Ajax 호출에 적용하면 다음과 같은 약속을 사용할 수 있습니다.
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
약속 한 모든 이점을 설명하는 것은이 답변의 범위를 벗어나지 만 새 코드를 작성하는 경우 신중하게 고려해야합니다. 그것들은 코드의 추상화와 분리를 제공합니다.
약속에 대한 추가 정보 : HTML5 Rocks-JavaScript Promises
참고 사항 : jQuery의 지연된 객체
지연된 객체 는 Promise API가 표준화되기 전에 jQuery의 사용자 지정 약속 구현입니다. 그들은 거의 약속처럼 행동하지만 약간 다른 API를 노출시킵니다.
jQuery의 모든 Ajax 메소드는 이미 함수에서 리턴 할 수있는 "지연된 오브젝트"(실제로 지연된 오브젝트의 약속)를 리턴합니다.
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
참고 사항 : Promise gotchas
약속 및 지연된 개체는 미래 가치를위한 컨테이너 일 뿐이며 값 자체는 아닙니다. 예를 들어, 다음이 있다고 가정하십시오.
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
이 코드는 위의 비동기 문제를 오해합니다. 특히 $.ajax()
서버에서 '/ password'페이지를 확인하는 동안 코드를 고정하지 않습니다. 서버에 요청을 보내고 대기하는 동안 서버의 응답이 아닌 jQuery Ajax Deferred 객체를 즉시 반환합니다. 이는 if
명령문이 항상이 Deferred 오브젝트를 가져 와서로 취급 true
하고 사용자가 로그인 한 것처럼 진행 함을 의미합니다. 좋지 않습니다.
그러나 수정은 쉽습니다.
checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
권장하지 않음 : 동기 "Ajax"호출
내가 언급했듯이, 일부 (!) 비동기 작업에는 동기 상대방이 있습니다. 나는 그들의 사용을 옹호하지는 않지만, 완전성을 위해 동기 호출을 수행하는 방법은 다음과 같습니다.
jQuery없이
XMLHTTPRequest
객체 를 직접 사용하는 경우에 false
세 번째 인수로 전달하십시오 .open
.
jQuery
jQuery 를 사용하는 경우 async
옵션을로 설정할 수 있습니다 false
. 이 옵션은 jQuery 1.8부터 더 이상 사용되지 않습니다 . 그런 다음 여전히 success
콜백을 사용 하거나 jqXHR 객체 의 responseText
속성에 액세스 할 수 있습니다 .
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
당신이 다른 jQuery를 아약스 방법 등 사용하는 경우 $.get
, $.getJSON
등, 당신은 그것을 변경해야 $.ajax
(당신은 단지에 구성 매개 변수를 전달할 수 있기 때문에 $.ajax
).
헤즈 업! 동기 JSONP 요청 은 불가능합니다 . JSONP는 본질적으로 항상 비동기 적입니다 (이 옵션을 고려하지 않는 또 하나의 이유).