질문자 :Felix Kling
비동기 요청을 만드는 foo
함수가 있습니다. foo
에서 응답/결과를 어떻게 반환할 수 있습니까?
콜백에서 값을 반환하고 함수 내부의 로컬 변수에 결과를 할당하고 그 값을 반환하려고 하지만 이러한 방법 중 어느 것도 실제로 응답을 반환하지 undefined
거나 초기 값이 무엇이든 반환 변수 result
는)입니다.
jQuery의 ajax
함수를 사용한 예:
function foo() { var result; $.ajax({ url: '...', success: function(response) { result = response; // return response; // <- I tried that one as well } }); return result; // It always returns `undefined` }
Node.js를 사용한 예:
function foo() { var result; fs.readFile("path/to/file", function(err, data) { result = data; // return data; // <- I tried that one as well }); return result; // It always returns `undefined` }
then
블록을 사용하는 예:
function foo() { var result; fetch(url).then(function(response) { result = response; // return response; // <- I tried that one as well }); return result; // It always returns `undefined` }
답변자 : Felix Kling
→ 다른 예를 사용한 비동기 동작에 대한 보다 일반적인 설명은 함수 내부에서 변수를 수정한 후 변수가 변경되지 않는 이유는 무엇입니까?를 참조하십시오. - 비동기 코드 참조
→ 이미 문제를 이해했다면 아래 가능한 해결 방법으로 건너뜁니다.
문제
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 the item }); doSomethingElse();
응답을 기다리는 대신 실행이 즉시 계속되고 Ajax 호출 후 명령문이 실행됩니다. 결국 응답을 얻으려면, 당신은 응답이 수신되면 호출되는 함수, 콜백 제공 (통지 뭔가? 전화 다시?). 해당 호출 이후에 오는 모든 명령문은 콜백이 호출되기 전에 실행됩니다.
솔루션
JavaScript의 비동기 특성을 수용하십시오! 특정 비동기 작업은 동기 작업을 제공하지만("Ajax"도 마찬가지임) 일반적으로 특히 브라우저 컨텍스트에서 사용하지 않는 것이 좋습니다.
왜 나쁘냐고 묻는거야?
JavaScript는 브라우저의 UI 스레드에서 실행되며 오래 실행되는 프로세스는 UI를 잠그고 응답하지 않게 만듭니다. 또한 JavaScript의 실행 시간에는 상한선이 있으며 브라우저는 사용자에게 실행을 계속할지 여부를 묻습니다.
이 모든 것은 정말 나쁜 사용자 경험을 초래합니다. 사용자는 모든 것이 잘 작동하는지 여부를 알 수 없습니다. 또한 연결 속도가 느린 사용자의 경우 효과가 더 나쁩니다.
다음에서는 서로를 기반으로 구축되는 세 가지 솔루션을 살펴보겠습니다.
-
async/await
약속 (ES2017+, 트랜스파일러 또는 재생기를 사용하는 경우 이전 브라우저에서 사용 가능) - 콜백 (노드에서 인기 있음)
-
then()
사용한 프라 미스(ES2015+, 많은 프라미스 라이브러리 중 하나를 사용하는 경우 이전 브라우저에서 사용 가능)
세 가지 모두 현재 브라우저와 노드 7+에서 사용할 수 있습니다.
2017년에 출시된 ECMAScript 버전에는 비동기 함수에 대한 구문 수준 지원이 도입되었습니다. async
및 await
의 도움으로 "동기식 스타일"로 비동기식으로 작성할 수 있습니다. 코드는 여전히 비동기식이지만 읽기/이해하기가 더 쉽습니다.
async/await
는 약속을 기반으로 합니다. async
함수는 항상 약속을 반환합니다. await
는 약속을 "풀기"하고 약속이 해결된 값을 초래하거나 약속이 거부된 경우 오류를 발생시킵니다.
중요: async
함수 내 await
사용할 수 있습니다. 현재 최상위 수준의 await
는 아직 지원되지 않으므로 async
컨텍스트를 시작하려면 비동기 IIFE( 즉시 호출된 함수 표현식 )를 만들어야 할 수도 있습니다.
async
및 await
에 대한 자세한 내용은 MDN에서 확인할 수 있습니다.
다음은 위 의 지연 함수 findItem()
을 자세히 설명하는 예입니다.
// 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로 변환하여 이전 환경을 지원할 수도 있습니다.
함수가 콜백을 수락하도록 허용
콜백은 함수 1이 함수 2에 전달될 때입니다. 함수 2는 준비가 될 때마다 함수 1을 호출할 수 있습니다. 비동기 프로세스의 컨텍스트에서 비동기 프로세스가 완료될 때마다 콜백이 호출됩니다. 일반적으로 결과는 콜백으로 전달됩니다.
질문의 예에서 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 응답을 받는 것은 이벤트일 뿐입니다. 타사 코드로 작업해야 하는 경우 어려움이 발생할 수 있지만 대부분의 문제는 응용 프로그램 흐름을 생각하는 것만으로도 해결할 수 있습니다.
Promise API 는 ECMAScript 6(ES2015)의 새로운 기능이지만 이미 좋은 브라우저 지원 을 제공합니다. 또한 표준 Promises API를 구현하고 비동기 함수(예: bluebird )의 사용 및 구성을 용이하게 하는 추가 메서드를 제공하는 라이브러리가 많이 있습니다.
약속은 미래 가치를 담는 그릇입니다. 프라미스가 값을 받거나(해결됨 ) 취소되면( 거부됨 ) 이 값에 액세스하려는 모든 "수신자"에게 알립니다.
일반 콜백에 비해 장점은 코드를 분리할 수 있고 작성이 더 쉽다는 것입니다.
다음은 약속을 사용하는 예입니다.
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). });
.as-console-wrapper { max-height: 100% !important; top: 0; }
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("https://jsonplaceholder.typicode.com/todos/1") .then(function(result) { console.log(result); // Code depending on result }) .catch(function() { // An error occurred });
.as-console-wrapper { max-height: 100% !important; top: 0; }
promise가 제공하는 모든 이점을 설명하는 것은 이 답변의 범위를 벗어나지만 새 코드를 작성하는 경우 진지하게 고려해야 합니다. 그들은 코드의 훌륭한 추상화와 분리를 제공합니다.
약속에 대한 추가 정보: HTML5 바위 - JavaScript 약속 .
참고: jQuery의 지연된 객체
지연된 객체 는 jQuery의 사용자 정의 약속 구현입니다(Promise API가 표준화되기 전). 그들은 거의 약속처럼 행동하지만 약간 다른 API를 노출합니다.
jQuery의 모든 Ajax 메서드는 이미 함수에서 반환할 수 있는 "지연된 객체"(실제로는 지연된 객체의 약속)를 반환합니다.
function ajax() { return $.ajax(...); } ajax().done(function(result) { // Code depending on result }).fail(function() { // An error occurred });
참고 사항: 약속 잡기
Promise와 Deferred 객체는 미래 가치를 담는 컨테이너일 뿐 가치 자체가 아니라는 점을 명심하십시오. 예를 들어 다음이 있다고 가정합니다.
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
객체를 직접 사용하는 경우 .open
세 번째 인수로 false
를 전달합니다.
제이쿼리
jQuery 를 사용하는 경우 async
옵션을 false
설정할 수 있습니다. 이 옵션은 jQuery 1.8부터 더 이상 사용되지 않습니다. 그런 다음 여전히 success
콜백을 사용하거나 jqXHR 객체 responseText
속성에 액세스할 수 있습니다.
function foo() { var jqXHR = $.ajax({ //... async: false }); return jqXHR.responseText; }
$.get
, $.getJSON
등과 같은 다른 jQuery Ajax 메소드를 사용하는 경우 $.ajax
로 변경해야 합니다(구성 매개변수만 $.ajax
전달할 수 있기 때문).
머리! 동기 JSONP 요청을 할 수 없습니다. JSONP는 본질적으로 항상 비동기식입니다(이 옵션을 고려하지 않는 또 다른 이유).
답변자 : Benjamin Gruenbaum
코드에서 jQuery를 사용하지 않는 경우 이 답변이 적합합니다.
코드는 다음과 같은 내용이어야 합니다.
function foo() { var httpRequest = new XMLHttpRequest(); httpRequest.open('GET', "/echo/json"); httpRequest.send(); return httpRequest.responseText; } var result = foo(); // Always ends up being 'undefined'
Felix Kling은 AJAX용 jQuery를 사용하는 사람들을 위한 답변을 훌륭하게 작성했지만 저는 그렇지 않은 사람들을 위한 대안을 제공하기로 결정했습니다.
( fetch
API, Angular 또는 promise를 사용하는 사람들을 위해 아래에 다른 답변을 추가했습니다. )
당신이 직면하고 있는
이것은 다른 답변의 "문제 설명"에 대한 간략한 요약입니다. 이것을 읽은 후 확실하지 않으면 읽으십시오.
AJAX의 A 는 비동기식을 나타냅니다. 이는 요청을 보내는 것(또는 오히려 응답을 받는 것)이 정상적인 실행 흐름에서 벗어남을 의미합니다. 귀하의 예에서 .send
는 즉시 반환하고 다음 문은 return result;
success
콜백으로 전달한 함수가 호출되기 전에 실행됩니다.
이것은 반환할 때 정의한 리스너가 아직 실행되지 않았음을 의미합니다. 이는 반환하는 값이 정의되지 않았음을 의미합니다.
다음은 간단한 비유입니다.
function getFive(){ var a; setTimeout(function(){ a=5; },10); return a; }
(깡깡이)
값 리턴이되고 a
undefined
때문에 a=5
부분이 아직 실행하지 않았다. AJAX는 이와 같이 작동합니다. 서버가 브라우저에 해당 값이 무엇인지 알려주기 전에 값을 반환하는 것입니다.
이 문제에 대한 한 가지 가능한 솔루션은 코드입니다 다시 적극적으로 계산이 완료 할 때 무엇을해야 하는지를 프로그램을 말하고.
function onComplete(a){ // When the code completes, do this alert(a); } function getFive(whenDone){ var a; setTimeout(function(){ a=5; whenDone(a); },10); }
이것을 CPS 라고 합니다. 기본적으로, 우리는 getFive
작업을 전달하고, 이벤트가 완료될 때(AJAX 호출 또는 이 경우 시간 초과와 같은) 반응하는 방법을 코드에 알려줍니다.
사용법은 다음과 같습니다.
getFive(onComplete);
화면에 "5"를 경고해야 합니다. (피들) .
가능한 해결책
이 문제를 해결하는 방법에는 기본적으로 두 가지가 있습니다.
- AJAX 호출을 동기식으로 만듭니다(SJAX라고 합시다).
- 콜백과 함께 제대로 작동하도록 코드를 재구성하십시오.
1. 동기식 AJAX - 하지마!!
동기식 AJAX 는 하지 마세요! Felix의 대답은 그것이 왜 나쁜 생각인지에 대한 몇 가지 설득력 있는 주장을 제기합니다. 요약하자면, 서버가 응답을 반환하고 매우 나쁜 사용자 경험을 생성할 때까지 사용자의 브라우저를 정지시킵니다. 다음은 그 이유에 대해 MDN에서 가져온 또 다른 짧은 요약입니다.
XMLHttpRequest는 동기 및 비동기 통신을 모두 지원합니다. 그러나 일반적으로 성능상의 이유로 비동기식 요청이 동기식 요청보다 선호되어야 합니다.
즉, 동기 요청은 코드 실행을 차단합니다... ...이는 심각한 문제를 일으킬 수 있습니다...
당신 이 그것을해야한다면, 당신은 깃발을 전달할 수 있습니다. 방법은 다음과 같습니다 .
var request = new XMLHttpRequest(); request.open('GET', 'yourURL', false); // `false` makes the request synchronous request.send(null); if (request.status === 200) {// That's HTTP for 'ok' console.log(request.responseText); }
2. 코드 재구성
함수가 콜백을 수락하도록 합니다. 예제 코드에서 foo
는 콜백을 수락하도록 만들 수 있습니다. foo
완료될 때 어떻게 반응 할지 코드에 알려줄 것입니다.
그래서:
var result = foo(); // Code that depends on `result` goes here
다음이 됩니다.
foo(function(result) { // Code that depends on `result` });
여기서는 익명 함수를 전달했지만 기존 함수에 대한 참조를 쉽게 전달하여 다음과 같이 만들 수 있습니다.
function myHandler(result) { // Code that depends on `result` } foo(myHandler);
이러한 종류의 콜백 디자인이 수행되는 방법에 대한 자세한 내용은 Felix의 답변을 확인하세요.
이제 foo 자체를 정의하여 그에 따라 행동하도록 합시다.
function foo(callback) { var httpRequest = new XMLHttpRequest(); httpRequest.onload = function(){ // When the request is loaded callback(httpRequest.responseText);// We're calling our method }; httpRequest.open('GET', "/echo/json"); httpRequest.send(); }
(깡깡이)
이제 foo 함수가 AJAX가 성공적으로 완료될 때 실행할 작업을 수락하도록 만들었습니다. 응답 상태가 200이 아닌지 확인하고 그에 따라 작동하여 이를 더 확장할 수 있습니다(실패 처리기 등 생성). 효과적으로 우리의 문제를 해결하고 있습니다.
여전히 이것을 이해하는 데 어려움이 있다면 MDN에서 AJAX 시작하기 가이드를 읽어보세요.
답변자 : cocco
XMLHttpRequest 2 (우선 Benjamin Gruenbaum 과 Felix Kling 의 답변을 읽어보세요)
jQuery를 사용하지 않고 최신 브라우저와 모바일 브라우저에서 작동하는 멋진 짧은 XMLHttpRequest 2를 원한다면 다음과 같이 사용하는 것이 좋습니다.
function ajax(a, b, c){ // URL, callback, just a placeholder c = new XMLHttpRequest; c.open('GET', a); c.onload = b; c.send() }
보시다시피:
- 나열된 다른 모든 기능보다 짧습니다.
- 콜백은 직접 설정됩니다(따라서 불필요한 클로저가 추가로 없음).
- 새로운 onload를 사용하므로 readystate && 상태를 확인할 필요가 없습니다.
- XMLHttpRequest 1을 성가시게 만드는 몇 가지 다른 상황이 있습니다.
이 Ajax 호출의 응답을 얻는 방법에는 두 가지가 있습니다(XMLHttpRequest var 이름을 사용하는 세 가지).
가장 간단한 것:
this.response
또는 어떤 이유로 콜백을 클래스에 bind()
e.target.response
예시:
function callback(e){ console.log(this.response); } ajax('URL', callback);
또는 (위의 것이 더 나은 익명 함수는 항상 문제입니다):
ajax('URL', function(e){console.log(this.response)});
더 쉬운 것은 없습니다.
이제 어떤 사람들은 onreadystatechange나 XMLHttpRequest 변수 이름을 사용하는 것이 더 낫다고 말할 것입니다. 그건 틀렸어요.
XMLHttpRequest 고급 기능을 확인하십시오.
모든 *최신 브라우저를 지원했습니다. 그리고 XMLHttpRequest 2가 생성된 이후로 이 방식을 사용하고 있음을 확인할 수 있습니다. 내가 사용한 모든 브라우저에서 어떤 유형의 문제도 발생하지 않았습니다.
onreadystatechange는 상태 2의 헤더를 가져오려는 경우에만 유용합니다.
XMLHttpRequest
변수 이름을 사용하는 것은 onload/oreadystatechange 클로저 내에서 콜백을 실행해야 하기 때문에 또 다른 큰 오류입니다.
이제 POST 및 FormData를 사용하여 더 복잡한 것을 원하면 이 함수를 쉽게 확장할 수 있습니다.
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder c = new XMLHttpRequest; c.open(e||'get', a); c.onload = b; c.send(d||null) }
다시 ... 매우 짧은 기능이지만 GET 및 POST를 수행합니다.
사용 예:
x(url, callback); // By default it's GET so no need to set x(url, callback, 'post', {'key': 'val'}); // No need to set POST data
또는 전체 양식 요소( document.getElementsByTagName('form')[0]
)를 전달합니다.
var fd = new FormData(form); x(url, callback, 'post', fd);
또는 일부 사용자 정의 값을 설정하십시오.
var fd = new FormData(); fd.append('key', 'val') x(url, callback, 'post', fd);
보시다시피 동기화를 구현하지 않았습니다... 나쁜 일입니다.
그런 다음 ... 쉬운 방법으로 하지 않겠습니까?
주석에서 언급했듯이 오류 && 동기를 사용하면 답변의 요점이 완전히 깨집니다. 적절한 방식으로 Ajax를 사용하는 좋은 짧은 방법은 무엇입니까?
오류 처리기
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder c = new XMLHttpRequest; c.open(e||'get', a); c.onload = b; c.onerror = error; c.send(d||null) } function error(e){ console.log('--Error--', this.type); console.log('this: ', this); console.log('Event: ', e) } function displayAjax(e){ console.log(e, this); } x('WRONGURL', displayAjax);
위의 스크립트에는 정적으로 정의된 오류 처리기가 있으므로 함수를 손상시키지 않습니다. 오류 처리기는 다른 기능에도 사용할 수 있습니다.
그러나 실제로 오류를 해결하려면 모든 브라우저에서 오류가 발생하는 잘못된 URL을 작성하는 것이 유일한 방법입니다.
오류 처리기는 사용자 정의 헤더를 설정하거나 responseType을 blob 배열 버퍼로 설정하는 경우 등에 유용할 수 있습니다.
메서드로 'POSTAPAPAP'을 전달해도 오류가 발생하지 않습니다.
'fdggdgilfdghfldj'를 formdata로 전달해도 오류가 발생하지 않습니다.
첫 번째 경우 오류는 this.statusText
아래 displayAjax()
Method not Allowed
있습니다.
두 번째 경우에는 단순히 작동합니다. 올바른 게시물 데이터를 전달했는지 서버 측에서 확인해야합니다.
교차 도메인이 허용되지 않으면 자동으로 오류가 발생합니다.
오류 응답에는 오류 코드가 없습니다.
error 로 설정된 this.type
만 있습니다.
오류를 완전히 제어할 수 없는 경우 오류 처리기를 추가하는 이유는 무엇입니까? 대부분의 오류는 콜백 함수 displayAjax()
내부에서 반환됩니다.
따라서 URL을 올바르게 복사하여 붙여넣을 수 있다면 오류 검사가 필요하지 않습니다. ;)
추신: 첫 번째 테스트로 x('x', displayAjax)...를 작성했고 완전히 응답을 받았습니다...??? 그래서 HTML이 있는 폴더를 확인해보니 'x.xml'이라는 파일이 있었습니다. 따라서 XMLHttpRequest 2 파일의 확장자를 잊어버리더라도 찾을 수 있습니다 . 난 웃었어
파일을 동기적으로 읽기
그러지 마세요.
잠시 동안 브라우저를 차단하려면 멋진 큰 .txt
파일을 동기식으로 로드하세요.
function omg(a, c){ // URL c = new XMLHttpRequest; c.open('GET', a, true); c.send(); return c; // Or c.response }
이제 할 수 있습니다
var res = omg('thisIsGonnaBlockThePage.txt');
비동기식으로 이 작업을 수행하는 다른 방법은 없습니다. (예, setTimeout 루프를 사용하지만... 진지하게?)
또 다른 요점은... API로 작업하거나 자신의 목록 파일만 사용하거나 각 요청에 대해 항상 다른 기능을 사용하는 경우...
항상 동일한 XML/JSON을 로드하거나 하나의 기능만 필요한 페이지가 있는 경우에만 가능합니다. 이 경우 Ajax 함수를 약간 수정하고 b를 특수 함수로 바꾸십시오.
위의 기능은 기본적으로 사용하기 위한 것입니다.
기능을 확장 하려면...
그래 넌 할수있어.
저는 많은 API를 사용하고 있으며 모든 HTML 페이지에 통합하는 첫 번째 기능 중 하나는 GET만 있는 이 답변의 첫 번째 Ajax 기능입니다...
그러나 XMLHttpRequest 2로 많은 일을 할 수 있습니다.
저는 다운로드 관리자(이력서, 파일 판독기 및 파일 시스템이 있는 양쪽의 범위 사용), 캔버스를 사용하는 다양한 이미지 크기 조정 변환기, base64images로 웹 SQL 데이터베이스 채우기 등을 만들었습니다.
그러나 이러한 경우에는 해당 목적으로만 함수를 생성해야 합니다... 때로는 blob, 배열 버퍼가 필요하고 헤더를 설정할 수 있고 mimetype을 재정의할 수 있으며 더 많은 것이 있습니다...
그러나 여기서 질문은 Ajax 응답을 반환하는 방법입니다... (쉬운 방법을 추가했습니다.)
답변자 : Benjamin Gruenbaum
약속을 사용하는 경우 이 답변이 적합합니다.
이것은 AngularJS, jQuery(지연됨), 기본 XHR 의 교체(fetch), Ember.js , Backbone.js 의 저장 또는 약속을 반환하는 모든 Node.js 라이브러리를 의미합니다.
코드는 다음과 같은 내용이어야 합니다.
function foo() { var data; // Or $.get(...).then, or request(...).then, or query(...).then fetch("/echo/json").then(function(response){ data = response.json(); }); return data; } var result = foo(); // 'result' is always undefined no matter what.
Felix Kling은 Ajax용 콜백과 함께 jQuery를 사용하는 사람들을 위한 답변을 훌륭하게 작성했습니다. 기본 XHR에 대한 답변이 있습니다. 이 답변은 프론트엔드나 백엔드에서 프라미스의 일반적인 사용을 위한 것입니다.
핵심 문제
브라우저와 Node.js/io.js가 있는 서버의 JavaScript 동시성 모델은 비동기식 및 반응형 입니다.
당신은 약속, 수익하는 메소드를 호출 할 때마다 then
A의 아닌 그 아래의 코드 후입니다 - 핸들러는 항상 비동기 적으로 실행되는 .then
핸들러를.
당신이 반환 할 때이 방법은 data
은 then
아직 실행하지 못했습니다 정의한 핸들러입니다. 이는 반환하는 값이 제때에 올바른 값으로 설정되지 않았음을 의미합니다.
다음은 문제에 대한 간단한 비유입니다.
function getFive(){ var data; setTimeout(function(){ // Set a timer for one second in the future data = 5; // After a second, do this }, 1000); return data; } document.body.innerHTML = getFive(); // `undefined` here and not 5
data = 5
부분이 아직 실행되지 않았기 때문에 data
값이 undefined
1초 안에 실행될 가능성이 높지만 그때까지는 반환된 값과 관련이 없습니다.
작업이 아직 발생하지 않았으므로(Ajax, 서버 호출, I/O 및 타이머) 요청이 코드에 해당 값이 무엇인지 알려주기 전에 값을 반환합니다.
이 문제에 대한 한 가지 가능한 솔루션은 코드입니다 다시 적극적으로 계산이 완료 할 때 무엇을해야 하는지를 프로그램을 말하고. 약속은 본질적으로 시간적(시간에 민감한)이 됨으로써 적극적으로 이를 가능하게 합니다.
약속에 대한 빠른 요약
약속은 시간이 지남에 따라 가치입니다. 약속에는 상태가 있습니다. 값이 없는 보류 상태로 시작하여 다음과 같이 정산할 수 있습니다.
- fulfilled 는 계산이 성공적으로 완료되었음을 의미합니다.
- 거부 는 계산이 실패했음을 의미합니다.
Promise는 상태를 한 번만 변경할 수 있으며 이후에는 항상 동일한 상태로 유지됩니다. then
핸들러를 promise에 연결하여 값을 추출하고 오류를 처리할 수 있습니다. then
핸들러는 호출 체인을 허용합니다. 약속을 반환하는 API를 사용하여 약속을 만듭니다. 예를 들어, 보다 현대적인 Ajax 교체 fetch
또는 jQuery의 $.get
반환 약속이 있습니다.
.then
에 대해 .then을 호출하고 그로부터 무언가를 반환 할 때 처리된 값 에 대한 프라미스를 얻습니다. 우리가 또 다른 약속을 하면 우리는 놀라운 것을 얻게 될 것이지만 말을 붙잡아 두자.
약속으로
Promise로 위의 문제를 어떻게 해결할 수 있는지 봅시다. 먼저 지연 함수를 생성하기 위해 Promise 생성자 를 사용하여 위에서 약속 상태에 대한 이해를 보여줍시다.
function delay(ms){ // Takes amount of milliseconds // Returns a new promise return new Promise(function(resolve, reject){ setTimeout(function(){ // When the time is up, resolve(); // change the promise to the fulfilled state }, ms); }); }
이제 약속을 사용하도록 setTimeout 을 변환한 후 then
을 사용하여 계산할 수 있습니다.
function delay(ms){ // Takes amount of milliseconds // Returns a new promise return new Promise(function(resolve, reject){ setTimeout(function(){ // When the time is up, resolve(); // change the promise to the fulfilled state }, ms); }); } function getFive(){ // We're RETURNING the promise. Remember, a promise is a wrapper over our value return delay(100).then(function(){ // When the promise is ready, return 5; // return the value 5. Promises are all about return values }) } // We _have_ to wrap it like this in the call site, and we can't access the plain value getFive().then(function(five){ document.body.innerHTML = five; });
기본적으로 동시성 모델 때문에 수행할 수 없는 값 을 반환하는 대신 then
으로 래핑 해제 할 수 있는 값에 대한 래퍼를 반환합니다. 그것은 당신이 열 수있는 상자처럼 then
.
이것을 적용
이는 원래 API 호출과 동일하며 다음을 수행할 수 있습니다.
function foo() { // RETURN the promise return fetch("/echo/json").then(function(response){ return response.json(); // Process it inside the `then` }); } foo().then(function(response){ // Access the value inside the `then` })
따라서 이것은 잘 작동합니다. 우리는 이미 비동기식 호출에서 값을 반환할 수 없다는 것을 배웠지만 약속을 사용하고 처리를 수행하기 위해 연결할 수 있습니다. 이제 비동기 호출에서 응답을 반환하는 방법을 알게 되었습니다.
ES2015 (ES6)
ES6에는 중간에 반환된 다음 원래 있던 지점을 다시 시작할 수 있는 함수인 생성기가 도입되었습니다. 이것은 일반적으로 시퀀스에 유용합니다. 예를 들면 다음과 같습니다.
function* foo(){ // Notice the star. This is ES6, so new browsers, Nodes.js, and io.js only yield 1; yield 2; while(true) yield 3; }
반복될 수 있는 시퀀스 1,2,3,3,3,3,....
대해 반복자 를 반환하는 함수입니다. 이것은 그 자체로 흥미롭고 많은 가능성을 열어주지만, 특히 흥미로운 사례가 하나 있습니다.
생성하는 시퀀스가 숫자가 아닌 일련의 작업인 경우 작업이 생성될 때마다 함수를 일시 중지하고 함수를 다시 시작하기 전에 기다릴 수 있습니다. 따라서 일련의 숫자 대신 미래 값의 시퀀스, 즉 약속이 필요합니다.
이것은 다소 까다롭지만 매우 강력한 트릭입니다. 동기 방식으로 비동기 코드를 작성해 보겠습니다. 당신을 위해 이것을 하는 몇몇 "주자"가 있습니다. 코드를 작성하는 것은 몇 줄의 짧은 코드이지만 이 답변의 범위를 벗어납니다. 여기서는 Bluebird의 Promise.coroutine
co
또는 Q.async
와 같은 다른 래퍼가 있습니다.
var foo = coroutine(function*(){ var data = yield fetch("/echo/json"); // Notice the yield // The code here only executes _after_ the request is done return data.json(); // 'data' is defined });
이 메서드는 다른 코루틴에서 사용할 수 있는 약속 자체를 반환합니다. 예를 들어:
var main = coroutine(function*(){ var bar = yield foo(); // Wait our earlier coroutine. It returns a promise // The server call is done here, and the code below executes when done var baz = yield fetch("/api/users/" + bar.userid); // Depends on foo's result console.log(baz); // Runs after both requests are done }); main();
ES2016 (ES7)
ES7에서는 더 표준화되었습니다. 현재 몇 가지 제안이 있지만 모두 약속 await
async
및 await
키워드를 추가하여 위의 ES6 제안에 대한 "설탕"(더 나은 구문)입니다. 위의 예를 만들기:
async function foo(){ var data = await fetch("/echo/json"); // Notice the await // code here only executes _after_ the request is done return data.json(); // 'data' is defined }
여전히 동일한 약속을 반환합니다. :)
답변자 : Nic
Ajax를 잘못 사용하고 있습니다. 아이디어는 아무 것도 반환하지 않고 대신 데이터를 처리하는 콜백 함수라는 것에 데이터를 넘겨주는 것입니다.
그건:
function handleData( responseData ) { // Do what you want with the data console.log(responseData); } $.ajax({ url: "hi.php", ... success: function ( data, status, XHR ) { handleData(data); } });
제출 핸들러에서 아무 것도 반환하지 않으면 아무 작업도 수행되지 않습니다. 대신 데이터를 전달하거나 성공 함수 내에서 직접 데이터로 원하는 작업을 수행해야 합니다.
답변자 : Johannes Fahrenkrug
끔찍하게 생긴 손으로 그린 만화로 보답하겠습니다. 두 번째 이미지는 코드 예제에서 result
가 undefined
답변자 : Hemant Bavle
가장 간단한 솔루션은 JavaScript 함수를 만들고 Ajax success
콜백을 위해 호출하는 것입니다.
function callServerAsync(){ $.ajax({ url: '...', success: function(response) { successCallback(response); } }); } function successCallback(responseObj){ // Do something like read the response and show data alert(JSON.stringify(responseObj)); // Only applicable to a JSON response } function foo(callback) { $.ajax({ url: '...', success: function(response) { return callback(null, response); } }); } var result = foo(function(err, result){ if (!err) console.log(result); });
답변자 : Maleen Abewardana
각도 1
AngularJS 를 사용하는 사람들은 promise를 사용하여 이러한 상황을 처리할 수 있습니다.
여기에서 말합니다.
Promise는 비동기 함수의 중첩을 해제하는 데 사용할 수 있으며 여러 함수를 함께 연결할 수 있습니다.
여기 에서도 좋은 설명을 찾을 수 있습니다.
아래에 언급 된 문서 에서 찾은 예입니다.
promiseB = promiseA.then( function onSuccess(result) { return result + 1; } ,function onError(err) { // Handle error } ); // promiseB will be resolved immediately after promiseA is resolved // and its value will be the result of promiseA incremented by 1.
Angular 2 이상
다음 예에서 모양과 각도 2, 그러나 그것에서 추천 코너 2와 함께 사용하기에 관찰 가능한에.
search(term: string) { return this.http .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`) .map((response) => response.json()) .toPromise(); }
이런 식으로 섭취할 수 있고,
search() { this.searchService.search(this.searchField.value) .then((result) => { this.result = result.artists.items; }) .catch((error) => console.error(error)); }
여기에서 원본 게시물을 참조하세요. 그러나 TypeScript는 기본 ES6 Promises를 지원하지 않습니다. 사용하려면 플러그인이 필요할 수 있습니다.
또한 다음은 약속 사양 입니다.
답변자 : T.J. Crowder
여기에 있는 대부분의 답변은 단일 비동기 작업이 있을 때 유용한 제안을 제공하지만 때로는 배열 또는 기타 목록과 유사한 구조의 각 항목에 대해 비동기 작업을 수행해야 할 때 나타납니다. 다음과 같이 하고 싶은 유혹이 있습니다.
// WRONG var results = []; theArray.forEach(function(entry) { doSomethingAsync(entry, function(result) { results.push(result); }); }); console.log(results); // Eg, using them, returning them, etc.
예시:
// WRONG var theArray = [1, 2, 3]; var results = []; theArray.forEach(function(entry) { doSomethingAsync(entry, function(result) { results.push(result); }); }); console.log("Results:", results); // Eg, using them, returning them, etc. function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
작동하지 않는 이유 doSomethingAsync
의 콜백이 아직 실행되지 않았기 때문입니다.
따라서 배열(또는 일종의 목록)이 있고 각 항목에 대해 비동기 작업을 수행하려는 경우 두 가지 옵션이 있습니다. 작업을 병렬로(중첩) 또는 직렬로(순서대로 하나씩) 수행합니다.
평행 한
모두 시작하고 예상되는 콜백 수를 추적한 다음 많은 콜백을 받았을 때 결과를 사용할 수 있습니다.
var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! console.log("Results:", results); // Eg, using the results } }); });
예시:
var theArray = [1, 2, 3]; var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! console.log("Results:", JSON.stringify(results)); // Eg, using the results } }); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
expecting
하지 않고 results.length === theArray.length
사용할 수 있지만 호출이 처리되지 않는 동안 theArray
가 변경될 가능성이 있습니다...)
결과가 잘못된 순서로 도착하더라도(비동기 호출이 시작된 순서대로 완료될 필요가 없기 때문에) forEach
index
를 results
를 관련된 항목과 동일한 위치에 저장하는 방법에 주목하세요. ).
하지만 함수에서 해당 결과 를 반환 해야 하는 경우에는 어떻게 될까요? 다른 답변에서 지적했듯이 할 수 없습니다. 함수가 콜백을 수락하고 호출하도록 해야 합니다(또는 Promise를 반환해야 함). 다음은 콜백 버전입니다.
function doSomethingWith(theArray, callback) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! callback(results); } }); }); } doSomethingWith(theArray, function(results) { console.log("Results:", results); });
예시:
function doSomethingWith(theArray, callback) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! callback(results); } }); }); } doSomethingWith([1, 2, 3], function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
또는 대신 Promise
를 반환하는 버전이 있습니다.
function doSomethingWith(theArray) { return new Promise(function(resolve) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! resolve(results); } }); }); }); } doSomethingWith(theArray).then(function(results) { console.log("Results:", results); });
물론 doSomethingAsync
가 오류를 전달했다면 오류가 발생했을 때 reject
를 사용하여 약속을 거부할 것입니다.)
예시:
function doSomethingWith(theArray) { return new Promise(function(resolve) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! resolve(results); } }); }); }); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
(또는 대안 doSomethingAsync
대한 래퍼를 만든 다음 아래를 수행할 수 있습니다...)
doSomethingAsync
가 Promise를 제공하면 Promise.all
을 사용할 수 있습니다.
function doSomethingWith(theArray) { return Promise.all(theArray.map(function(entry) { return doSomethingAsync(entry); })); } doSomethingWith(theArray).then(function(results) { console.log("Results:", results); });
doSomethingAsync
가 두 번째 및 세 번째 인수를 무시한다는 것을 알고 있다면 map
직접 전달할 수 있습니다( map
은 3개의 인수로 콜백을 호출하지만 대부분의 사람들은 대부분 첫 번째 인수만 사용합니다).
function doSomethingWith(theArray) { return Promise.all(theArray.map(doSomethingAsync)); } doSomethingWith(theArray).then(function(results) { console.log("Results:", results); });
예시:
function doSomethingWith(theArray) { return Promise.all(theArray.map(doSomethingAsync)); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
Promise.all
은 약속이 모두 해결되었을 때 제공한 모든 약속의 결과 배열로 약속을 해결하거나 첫 번째 약속이 거부되면 약속을 거부합니다.
시리즈
작업을 병렬로 수행하고 싶지 않다고 가정해 보겠습니다. 이를 차례로 실행하려면 다음 작업을 시작하기 전에 각 작업이 완료될 때까지 기다려야 합니다. 다음은 이를 수행하고 결과로 콜백을 호출하는 함수의 예입니다.
function doSomethingWith(theArray, callback) { var results = []; doOne(0); function doOne(index) { if (index < theArray.length) { doSomethingAsync(theArray[index], function(result) { results.push(result); doOne(index + 1); }); } else { // Done! callback(results); } } } doSomethingWith(theArray, function(results) { console.log("Results:", results); });
(우리는 일련의 작업을 수행하기 때문에 결과를 순서대로 얻지 않는다는 것을 알고 있기 때문에 results.push(result)
results[index] = result;
를 사용할 수 있었지만 다음 예 중 일부에서는 사용할 인덱스가 없습니다.)
예시:
function doSomethingWith(theArray, callback) { var results = []; doOne(0); function doOne(index) { if (index < theArray.length) { doSomethingAsync(theArray[index], function(result) { results.push(result); doOne(index + 1); }); } else { // Done! callback(results); } } } doSomethingWith([1, 2, 3], function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
(또는 다시, 약속을 제공하고 아래 작업을 수행하는 doSomethingAsync
doSomethingAsync
가 Promise를 제공하는 경우 ES2017+ 구문을 사용할 수 있다면(아마도 Babel 과 같은 변환기 사용), for-of
및 await
와 함께 async
함수 를 사용할 수 있습니다.
async function doSomethingWith(theArray) { const results = []; for (const entry of theArray) { results.push(await doSomethingAsync(entry)); } return results; } doSomethingWith(theArray).then(results => { console.log("Results:", results); });
예시:
async function doSomethingWith(theArray) { const results = []; for (const entry of theArray) { results.push(await doSomethingAsync(entry)); } return results; } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
ES2017+ 구문(아직)을 사용할 수 없는 경우 "Promise Reduce" 패턴의 변형을 사용할 수 있습니다. 결과를 배열로 수집):
function doSomethingWith(theArray) { return theArray.reduce(function(p, entry) { return p.then(function(results) { return doSomethingAsync(entry).then(function(result) { results.push(result); return results; }); }); }, Promise.resolve([])); } doSomethingWith(theArray).then(function(results) { console.log("Results:", results); });
예시:
function doSomethingWith(theArray) { return theArray.reduce(function(p, entry) { return p.then(function(results) { return doSomethingAsync(entry).then(function(result) { results.push(result); return results; }); }); }, Promise.resolve([])); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
...ES2015+ 화살표 기능을 사용하면 덜 번거롭습니다.
function doSomethingWith(theArray) { return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => { results.push(result); return results; })), Promise.resolve([])); } doSomethingWith(theArray).then(results => { console.log("Results:", results); });
예시:
function doSomethingWith(theArray) { return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => { results.push(result); return results; })), Promise.resolve([])); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", JSON.stringify(results)); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
답변자 : Francisco Carmona
이 예를 살펴보십시오.
var app = angular.module('plunker', []); app.controller('MainCtrl', function($scope,$http) { var getJoke = function(){ return $http.get('http://api.icndb.com/jokes/random').then(function(res){ return res.data.value; }); } getJoke().then(function(res) { console.log(res.joke); }); });
보시다시피 getJoke
는 해결된 약속을 반환합니다( res.data.value를 반환할 때 res.data.value
). 따라서 $http.get 요청이 완료될 때까지 기다린 다음 console.log(res.joke) 가 실행됩니다(일반 비동기 흐름으로).
다음은 plnkr입니다.
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
ES6 방식(비동기 - 대기)
(function(){ async function getJoke(){ let response = await fetch('http://api.icndb.com/jokes/random'); let data = await response.json(); return data.value; } getJoke().then((joke) => { console.log(joke); }); })();
답변자 : Alireza
이것은 많은 새로운 JavaScript 프레임워크에서 사용되는 양방향 데이터 바인딩 또는 저장 개념 이 잘 작동하는 곳 중 하나입니다...
따라서 Angular , React 또는 양방향 데이터 바인딩 또는 저장 개념을 수행하는 다른 프레임워크를 사용하는 경우 이 문제는 간단히 해결되므로 쉽게 말하면 undefined
않으므로 다음을 얻을 수 있습니다. result = undefined
데이터를 수신하기 전에 결과를 얻자 마자 업데이트되고 Ajax 호출의 응답으로 새 값이 할당됩니다...
그러나 이 질문에서 질문한 것처럼 순수 JavaScript 또는 jQuery에서 어떻게 이를 수행할 수 있습니까?
콜백, 약속 및 최근 관찰 가능을 사용하여 처리할 수 있습니다. 예를 들어, 약속에는 데이터가 준비되면 실행되는 success()
또는 then()
옵저버블의 콜백 또는 구독 기능과 동일합니다.
예를 들어 jQuery를 사용하는 경우 다음과 같이 할 수 있습니다.
$(document).ready(function(){ function foo() { $.ajax({url: "api/data", success: function(data){ fooDone(data); // After we have data, we pass it to fooDone }}); }; function fooDone(data) { console.log(data); // fooDone has the data and console.log it }; foo(); // The call happens here });
자세한 내용은 이 비동기 작업을 수행하는 더 새로운 방법인 promise 및 observable을 연구하세요.
답변자 : Anish K.
JavaScript의 '미스터리'와 씨름하는 동안 직면하는 매우 일반적인 문제입니다. 오늘 이 신비를 풀어보도록 하겠습니다.
간단한 JavaScript 함수부터 시작하겠습니다.
function foo(){ // Do something return 'wohoo'; } let bar = foo(); // 'bar' is 'wohoo' here
이것은 간단한 동기 함수 호출(각 코드 줄이 순서대로 다음 줄보다 먼저 '작업으로 완료'됨)이며 결과는 예상한 것과 같습니다.
이제 함수에 약간의 지연을 도입하여 코드의 모든 라인이 순서대로 '완료'되지 않도록 약간의 비틀기를 추가해 보겠습니다. 따라서 함수의 비동기 동작을 에뮬레이트합니다.
function foo(){ setTimeout( ()=> { return 'wohoo'; }, 1000) } let bar = foo() // 'bar' is undefined here
그래서 당신은 간다; 그 지연은 우리가 기대했던 기능을 깨뜨렸습니다! 그러나 정확히 무슨 일이 일어났습니까? 글쎄요, 코드를 보면 꽤 논리적입니다.
foo()
함수는 실행 시 아무 것도 반환하지 않지만(따라서 반환된 값은 undefined
) 타이머를 시작합니다. 이 타이머는 1초 후에 함수를 실행하여 'woohoo'를 반환합니다. 그러나 보시다시피 bar 에 할당된 값은 foo() 에서 즉시 반환된 것입니다. 이것은 아무것도 아닙니다. 즉, undefined
입니다.
그렇다면 이 문제를 어떻게 해결해야 할까요?
우리의 함수에 promise 를 요청합시다. Promise는 실제로 의미하는 바에 관한 것입니다. 즉, 함수가 미래에 얻을 모든 출력을 제공할 수 있음을 의미합니다. 따라서 위의 작은 문제에 대해 실행해 보겠습니다.
function foo(){ return new Promise((resolve, reject) => { // I want foo() to PROMISE me something setTimeout ( function(){ // Promise is RESOLVED, when the execution reaches this line of code resolve('wohoo') // After 1 second, RESOLVE the promise with value 'wohoo' }, 1000 ) }) } let bar; foo().then( res => { bar = res; console.log(bar) // Will print 'wohoo' });
따라서, 요약입니다 - 등 Ajax 기반 호출 같은 비동기 기능을 해결하기 위해, 당신은 할 수있는 약속을 사용할 수 있습니다 resolve
(당신이 반환하려는) 값을. 따라서, 한마디로 당신은 비동기 기능에 대신 반환하는 값을 해결.
업데이트(async/await로 약속)
then/catch
를 사용하여 프라미스 작업을 하는 것 외에도 접근 방식이 하나 더 있습니다. 아이디어는 비동기 함수 를 인식하고 다음 코드 줄로 이동하기 전에 약속이 해결될 때까지 기다리는 것입니다. 이는 여전히 내부에 있는 promises
일 뿐이지만 구문론적 접근 방식은 다릅니다. 더 명확하게 하기 위해 아래에서 비교를 찾을 수 있습니다.
then/catch 버전:
function saveUsers(){ getUsers() .then(users => { saveSomewhere(users); }) .catch(err => { console.error(err); }) }
비동기/대기 버전:
async function saveUsers(){ try{ let users = await getUsers() saveSomewhere(users); } catch(err){ console.error(err); } }
답변자 : jsbisht
비동기 함수에서 값을 반환하는 또 다른 방법은 비동기 함수의 결과를 저장할 개체를 전달하는 것입니다.
다음은 동일한 예입니다.
var async = require("async"); // This wires up result back to the caller var result = {}; var asyncTasks = []; asyncTasks.push(function(_callback){ // some asynchronous operation $.ajax({ url: '...', success: function(response) { result.response = response; _callback(); } }); }); async.parallel(asyncTasks, function(){ // result is available after performing asynchronous operation console.log(result) console.log('Done'); });
result
개체를 사용하여 비동기 작업 중에 값을 저장하고 있습니다. 이렇게 하면 비동기 작업 후에도 결과를 사용할 수 있습니다.
저는 이 방법을 많이 사용합니다. 연속 모듈을 통해 결과를 다시 연결하는 경우 이 접근 방식이 얼마나 잘 작동하는지 알고 싶습니다.
답변자 : rohithpr
프라미스와 콜백은 많은 상황에서 잘 작동하지만 다음과 같은 것을 표현하는 것은 뒤에서 고통스럽습니다.
if (!name) { name = async1(); } async2(name);
결국 async1
; name
이 정의되지 않았는지 확인하고 그에 따라 콜백을 호출하십시오.
async1(name, callback) { if (name) callback(name) else { doSomething(callback) } } async1(name, async2)
작은 예에서는 괜찮지만 유사한 경우가 많고 오류 처리가 관련되어 있으면 짜증이 납니다.
Fibers
는 문제를 해결하는 데 도움이 됩니다.
var Fiber = require('fibers') function async1(container) { var current = Fiber.current var result doSomething(function(name) { result = name fiber.run() }) Fiber.yield() return result } Fiber(function() { var name if (!name) { name = async1() } async2(name) // Make any number of async calls from here }
여기 에서 프로젝트를 확인할 수 있습니다.
답변자 : loretoparisi
내가 작성한 다음 예제는
이 작업 예제는 독립적입니다. XMLHttpRequest
객체를 사용하여 호출하는 간단한 요청 객체를 정의합니다. 많은 약속이 완료될 때까지 기다리는 간단한 함수를 정의합니다.
문맥. 예제는 주어진 쿼리 문자열 집합에 playlist
개체를 검색하기 위해 Spotify Web API 끝점을 쿼리합니다.
[ "search?type=playlist&q=%22doom%20metal%22", "search?type=playlist&q=Adele" ]
각 항목에 대해 새 Promise는 블록( ExecutionBlock
하고 결과를 구문 분석하고 결과 배열(Spotify user
ExecutionProfileBlock
내에서 비동기식으로 새 HTTP 호출을 실행합니다.
Promise.all
통해 각 호출 하위 집합의 결과를 결합할 수 있는 중첩된 Promise 구조를 볼 수 있습니다.
참고 최근 Spotify search
API는 요청 헤더에 액세스 토큰을 지정해야 합니다.
-H "Authorization: Bearer {your access token}"
따라서 다음 예제를 실행하려면 요청 헤더에 액세스 토큰을 넣어야 합니다.
var spotifyAccessToken = "YourSpotifyAccessToken"; var console = { log: function(s) { document.getElementById("console").innerHTML += s + "<br/>" } } // Simple XMLHttpRequest // based on https://davidwalsh.name/xmlhttprequest SimpleRequest = { call: function(what, response) { var request; if (window.XMLHttpRequest) { // Mozilla, Safari, ... request = new XMLHttpRequest(); } else if (window.ActiveXObject) { // Internet Explorer try { request = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } // State changes request.onreadystatechange = function() { if (request.readyState === 4) { // Done if (request.status === 200) { // Complete response(request.responseText) } else response(); } } request.open('GET', what, true); request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken); request.send(null); } } //PromiseAll var promiseAll = function(items, block, done, fail) { var self = this; var promises = [], index = 0; items.forEach(function(item) { promises.push(function(item, i) { return new Promise(function(resolve, reject) { if (block) { block.apply(this, [item, index, resolve, reject]); } }); }(item, ++index)) }); Promise.all(promises).then(function AcceptHandler(results) { if (done) done(results); }, function ErrorHandler(error) { if (fail) fail(error); }); }; //promiseAll // LP: deferred execution block var ExecutionBlock = function(item, index, resolve, reject) { var url = "https://api.spotify.com/v1/" url += item; console.log( url ) SimpleRequest.call(url, function(result) { if (result) { var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) { return item.owner.href; }) resolve(profileUrls); } else { reject(new Error("call error")); } }) } arr = [ "search?type=playlist&q=%22doom%20metal%22", "search?type=playlist&q=Adele" ] promiseAll(arr, function(item, index, resolve, reject) { console.log("Making request [" + index + "]") ExecutionBlock(item, index, resolve, reject); }, function(results) { // Aggregated results console.log("All profiles received " + results.length); //console.log(JSON.stringify(results[0], null, 2)); ///// promiseall again var ExecutionProfileBlock = function(item, index, resolve, reject) { SimpleRequest.call(item, function(result) { if (result) { var obj = JSON.parse(result); resolve({ name: obj.display_name, followers: obj.followers.total, url: obj.href }); } //result }) } //ExecutionProfileBlock promiseAll(results[0], function(item, index, resolve, reject) { //console.log("Making request [" + index + "] " + item) ExecutionProfileBlock(item, index, resolve, reject); }, function(results) { // aggregated results console.log("All response received " + results.length); console.log(JSON.stringify(results, null, 2)); } , function(error) { // Error console.log(error); }) ///// }, function(error) { // Error console.log(error); });
<div id="console" />
여기 에서 이 솔루션에 대해 광범위하게 논의했습니다.
답변자 : Pablo Matias Gomez
짧은 대답은 다음과 같이 콜백을 구현해야 한다는 것입니다.
function callback(response) { // Here you can do what ever you want with the response object. console.log(response); } $.ajax({ url: "...", success: callback });
답변자 : Aniket Jha
JavaScript는 단일 스레드입니다.
브라우저는 세 부분으로 나눌 수 있습니다.
이벤트 루프
웹 API
이벤트 대기열
이벤트 루프는 영원히 실행됩니다. 즉, 일종의 무한 루프입니다. 이벤트 대기열은 일부 이벤트(예: 클릭)에서 모든 기능이 푸시되는 곳입니다.
이것은 큐에서 하나씩 수행되어 이 함수를 실행하고 첫 번째 함수가 실행된 후 다음 함수를 준비하는 이벤트 루프에 넣습니다. 즉, 한 함수의 실행은 대기열에 있는 함수가 이벤트 루프에서 실행되기 전까지 시작되지 않습니다.
이제 대기열에 두 개의 함수를 푸시했다고 가정해 보겠습니다. 하나는 서버에서 데이터를 가져오는 것이고 다른 하나는 해당 데이터를 활용하는 것입니다. 먼저 대기열에 serverRequest() 함수를 푸시한 다음 utiliseData() 함수를 푸시했습니다. serverRequest 함수는 이벤트 루프에 들어가서 서버에서 데이터를 가져오는 데 얼마나 많은 시간이 걸릴지 모르기 때문에 서버를 호출합니다.
그것이 Web API가 역할을 하는 곳입니다. 이벤트 루프에서 이 함수를 가져오고 서버를 처리하여 이벤트 루프를 무료로 만들어 대기열에서 다음 함수를 실행할 수 있습니다.
큐의 다음 함수는 루프에 들어가는 utiliseData()이지만 사용 가능한 데이터가 없기 때문에 낭비되고 다음 함수의 실행은 큐가 끝날 때까지 계속됩니다. (이를 비동기 호출이라고 합니다. 즉, 데이터를 얻을 때까지 다른 작업을 수행할 수 있습니다.)
우리의 serverRequest() 함수가 코드에 return 문을 가지고 있다고 가정해 봅시다. 서버 Web API에서 데이터를 다시 받으면 큐 끝의 큐에 푸시합니다.
큐의 끝에서 푸시되면 이 데이터를 활용하기 위해 큐에 남아 있는 기능이 없기 때문에 데이터를 활용할 수 없습니다. 따라서 비동기 호출에서 무언가를 반환하는 것은 불가능합니다.
따라서 이에 대한 해결책 은 callback 또는 promise 입니다.
우리는 우리의 함수(서버에서 반환된 데이터를 활용하는 함수)를 서버를 호출하는 함수에 제공합니다.
function doAjax(callbackFunc, method, url) { var xmlHttpReq = new XMLHttpRequest(); xmlHttpReq.open(method, url); xmlHttpReq.onreadystatechange = function() { if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) { callbackFunc(xmlHttpReq.responseText); } } xmlHttpReq.send(null); }
내 코드 에서는 다음과 같이 호출됩니다.
function loadMyJson(categoryValue){ if(categoryValue === "veg") doAjax(print, "GET", "http://localhost:3004/vegetables"); else if(categoryValue === "fruits") doAjax(print, "GET", "http://localhost:3004/fruits"); else console.log("Data not found"); }
JavaScript.info 콜백
답변자 : mikemaccana
이것은 매우 간단합니다.
- 약속을 돌려줘
- 'await' 를 사용하여 JavaScript가 값으로 확인될 때까지 기다리도록 합니다(예: HTTP 응답).
- 상위 함수에 'async' 키워드 추가
다음은 작동 중인 코드 버전입니다.
(async function(){ var response = await superagent.get('...') console.log(response) })()
await는 모든 현재 브라우저와 Node.js 8에서 지원됩니다.
답변자 : Vinoth Rajendran
이 사용자 지정 라이브러리(Promise를 사용하여 작성)를 사용하여 원격 호출을 할 수 있습니다.
function $http(apiConfig) { return new Promise(function (resolve, reject) { var client = new XMLHttpRequest(); client.open(apiConfig.method, apiConfig.url); client.send(); client.onload = function () { if (this.status >= 200 && this.status < 300) { // Performs the function "resolve" when this.status is equal to 2xx. // Your logic here. resolve(this.response); } else { // Performs the function "reject" when this.status is different than 2xx. reject(this.statusText); } }; client.onerror = function () { reject(this.statusText); }; }); }
간단한 사용 예:
$http({ method: 'get', url: 'google.com' }).then(function(response) { console.log(response); }, function(error) { console.log(error) });
답변자 : amaksr
또 다른 솔루션은 순차 실행기 nsynjs 를 통해 코드를 실행하는 것입니다.
기본 기능이 약속된 경우
nsynjs는 모든 약속을 순차적으로 평가하고 약속 결과를 data
속성에 넣습니다.
function synchronousCode() { var getURL = function(url) { return window.fetch(url).data.text().data; }; var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js'; console.log('received bytes:',getURL(url).length); }; nsynjs.run(synchronousCode,{},function(){ console.log('synchronousCode done'); });
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
기본 기능이 약속되지 않은 경우
1단계. 콜백이 있는 함수를 nsynjs 인식 래퍼로 래핑합니다(약속 버전이 있는 경우 이 단계를 건너뛸 수 있음).
var ajaxGet = function (ctx,url) { var res = {}; var ex; $.ajax(url) .done(function (data) { res.data = data; }) .fail(function(e) { ex = e; }) .always(function() { ctx.resume(ex); }); return res; }; ajaxGet.nsynjsHasCallback = true;
2단계. 동기 로직을 기능에 적용:
function process() { console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data); }
3단계. nsynjs를 통해 동기 방식으로 함수 실행:
nsynjs.run(process,this,function () { console.log("synchronous function finished"); });
Nsynjs는 모든 연산자와 표현식을 단계별로 평가하여 일부 느린 함수의 결과가 준비되지 않은 경우 실행을 일시 중지합니다.
더 많은 예가 여기에 있습니다 .
답변자 : James
ECMAScript 6에는 비동기식 스타일로 쉽게 프로그래밍할 수 있는 '제너레이터'가 있습니다.
function* myGenerator() { const callback = yield; let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback}); console.log("response is:", response); // examples of other things you can do yield setTimeout(callback, 1000); console.log("it delayed for 1000ms"); while (response.statusText === "error") { [response] = yield* anotherGenerator(); } }
위의 코드를 실행하려면 다음을 수행합니다.
const gen = myGenerator(); // Create generator gen.next(); // Start it gen.next((...args) => gen.next([...args])); // Set its callback function
ES6을 지원하지 않는 브라우저를 대상으로 해야 하는 경우 Babel 또는 클로저 컴파일러를 통해 코드를 실행하여 ECMAScript 5를 생성할 수 있습니다.
콜백 ...args
는 배열로 래핑되고 읽을 때 구조가 해제되어 패턴이 여러 인수가 있는 콜백에 대처할 수 있습니다. 예를 들어 노드 fs :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
답변자 : Community Wiki
우리는 우리가 "시간"이라고 부르는 차원을 따라 진행하는 것처럼 보이는 우주에 있습니다. 우리는 시간이 무엇인지 제대로 이해하지 못하지만, "과거", "현재", "미래", "이전", "이후"와 같이 추론하고 이야기할 수 있는 추상화와 어휘를 개발했습니다.
우리가 구축하는 컴퓨터 시스템은 점점 더 많은 시간을 중요한 차원으로 가지고 있습니다. 어떤 일이 미래에 일어나도록 설정되어 있습니다. 그런 다음 첫 번째 일이 결국 발생한 후에 다른 일이 일어나야 합니다. 이것이 "비동기성"이라는 기본 개념입니다. 점점 더 네트워크화되는 세상에서 가장 흔한 비동기화의 경우는 원격 시스템이 일부 요청에 응답하기를 기다리는 것입니다.
예를 들어 보십시오. 당신은 우유 배달원에게 전화를 걸어 우유를 주문합니다. 그것이 오면, 당신은 그것을 당신의 커피에 넣고 싶어합니다. 아직 커피에 우유가 없기 때문에 지금 당장은 우유를 넣을 수 없습니다. 커피에 넣기 전에 커피가 나올 때까지 기다려야 합니다. 즉, 다음은 작동하지 않습니다.
var milk = order_milk(); put_in_coffee(milk);
order_milk
를 실행하기 전에 order_milk가 완료될 때까지 기다려야 한다는 것을 알 방법이 없기 put_in_coffee
입니다. order_milk
가 비동기식 이라는 것을 알지 못합니다. 이것은 미래의 어느 시점까지 우유를 생산하지 않을 것입니다. JavaScript 및 기타 선언적 언어는 기다리지 않고 명령문을 차례로 실행합니다.
JavaScript가 전달될 수 있는 일급 객체로 함수를 지원한다는 사실을 활용하는 이 문제에 대한 고전적인 JavaScript 접근 방식은 비동기 요청에 매개변수로 함수를 전달한 다음 완료되면 호출합니다. 미래의 언젠가의 임무. 이것이 "콜백" 접근 방식입니다. 다음과 같습니다.
order_milk(put_in_coffee);
order_milk
가 시작되고 우유를 주문한 다음 도착할 때만 put_in_coffee
호출합니다.
return
보고하는 함수의 일반적인 의미를 오염시킨다는 것입니다. 대신 함수는 매개변수로 제공된 콜백을 호출하여 결과를 보고해서는 안 됩니다. 또한 이 접근 방식은 더 긴 이벤트 시퀀스를 처리할 때 빠르게 다루기 어려워질 수 있습니다. 예를 들어, 우유가 커피에 담길 때까지 기다렸다가 세 번째 단계인 커피를 마시고 싶다고 가정해 보겠습니다. 결국 다음과 같이 작성해야 합니다.
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
여기서 저는 우유를 넣을 우유와 우유를 넣은 후에 drink_coffee
put_in_coffee
전달합니다. 이러한 코드는 작성, 읽기 및 디버그하기 어려워집니다.
이 경우 질문의 코드를 다음과 같이 다시 작성할 수 있습니다.
var answer; $.ajax('/foo.json') . done(function(response) { callback(response.data); }); function callback(data) { console.log(data); }
약속 입력
이것은 "약속"이라는 개념에 대한 동기였으며, 이는 미래 또는 일종의 비동기 결과를 나타내는 특정 유형의 값입니다. 그것은 이미 일어난 일, 미래에 일어날 일, 전혀 일어나지 않을 일을 나타낼 수 있습니다. then
이라는 단일 메서드가 있으며, Promise가 나타내는 결과가 실현되었을 때 실행할 작업을 이 메서드에 전달합니다.
우유와 커피의 경우 우유 도착에 대한 약속을 반환하도록 order_milk
를 디자인한 then
다음과 같이 put_in_coffee
order_milk() . then(put_in_coffee)
이것의 한 가지 장점은 미래에 발생하는 시퀀스를 생성하기 위해 이것들을 함께 묶을 수 있다는 것입니다("체이닝"):
order_milk() . then(put_in_coffee) . then(drink_coffee)
특정 문제에 약속을 적용해 보겠습니다. 우리는 약속을 반환하는 함수 안에 요청 로직을 래핑할 것입니다:
function get_data() { return $.ajax('/foo.json'); }
실제로 우리가 한 일은 $.ajax
호출에 return
을 추가한 것뿐입니다. $.ajax
이미 일종의 약속 같은 것을 반환하기 때문에 작동합니다. (실제로는 세부 사항을 다루지 않고 이 호출을 래핑하여 실제 약속을 반환하거나 $.ajax
대한 대안을 사용하는 것을 선호합니다.) 이제 파일을 로드하고 기다리려면 끝내고 무언가를 하기 위해 우리는 간단히 말할 수 있습니다.
get_data() . then(do_something)
예를 들어,
get_data() . then(function(data) { console.log(data); });
Promise를 사용할 때, 우리는 많은 함수를 then
전달하게 되므로 보다 간결한 ES6 스타일 화살표 함수를 사용하는 것이 종종 도움이 됩니다.
get_data() . then(data => console.log(data));
async
키워드
그러나 동기식인 경우 한 방향으로 코드를 작성해야 하고 비동기식인 경우 완전히 다른 방식으로 코드를 작성해야 한다는 막연한 불만이 있습니다. 동기식의 경우 다음을 작성합니다.
a(); b();
하지만 만약 a
약속, 비동기 우리는 작성해야
a() . then(b);
위에서 "JavaScript는 두 번째 호출을 실행하기 전에 첫 번째 호출이 완료 될 때까지 기다려야 한다는 것을 알 수 있는 방법이 없습니다."라고 말했습니다. 자바스크립트에 그것을 알릴 수 있는 방법 이 있다면 좋지 않을까요? "async" 함수라고 하는 특별한 유형의 함수 내부에서 사용되는 await
키워드가 있음이 밝혀졌습니다. 이 기능은 향후 ECMAScript(ES) 버전의 일부이지만 올바른 사전 설정이 제공 되는 Babel과 같은 트랜스파일러에서 이미 사용할 수 있습니다. 이것은 우리가 단순히 쓸 수있게합니다
async function morning_routine() { var milk = await order_milk(); var coffee = await put_in_coffee(milk); await drink(coffee); }
귀하의 경우 다음과 같이 작성할 수 있습니다.
async function foo() { data = await get_data(); console.log(data); }
답변자 : David R Tribble
짧은 대답 : foo()
메서드는 즉시 반환되지만 $ajax()
호출은 함수가 반환된 후 비동기적으로 실행됩니다. 문제는 비동기 호출이 반환되면 검색된 결과를 저장하는 방법 또는 위치입니다.
이 스레드에서 몇 가지 솔루션이 제공되었습니다. 아마도 가장 쉬운 방법은 객체를 foo()
메서드에 전달하고 비동기 호출이 완료된 후 해당 객체의 멤버에 결과를 저장하는 것입니다.
function foo(result) { $.ajax({ url: '...', success: function(response) { result.response = response; // Store the async result } }); } var result = { response: null }; // Object to hold the async result foo(result); // Returns before the async completes
foo()
호출은 여전히 유용한 것을 반환하지 않습니다. 그러나 비동기 호출의 결과는 이제 result.response
저장됩니다.
답변자 : Mohan Dere
다음은 비동기 요청으로 작업하는 몇 가지 접근 방식입니다.
- 브라우저 약속 개체
- Q - JavaScript용 Promise 라이브러리
- A+ Promises.js
- jQuery 지연
- XMLHttpRequest API
- 콜백 개념 사용 - 첫 번째 답변의 구현으로
예: 여러 요청을 처리하기 위해 jQuery 지연 구현
var App = App || {}; App = { getDataFromServer: function(){ var self = this, deferred = $.Deferred(), requests = []; requests.push($.getJSON('request/ajax/url/1')); requests.push($.getJSON('request/ajax/url/2')); $.when.apply(jQuery, requests).done(function(xhrResponse) { return deferred.resolve(xhrResponse.result); }); return deferred; }, init: function(){ this.getDataFromServer().done(_.bind(function(resp1, resp2) { // Do the operations which you wanted to do when you // get a response from Ajax, for example, log response. }, this)); } }; App.init();
답변자 : Mahfuzur Rahman
foo()
성공 내부에서 callback()
함수를 사용하십시오. 이런 식으로 시도하십시오. 간단하고 이해하기 쉽습니다.
var lat = ""; var lon = ""; function callback(data) { lat = data.lat; lon = data.lon; } function getLoc() { var url = "http://ip-api.com/json" $.getJSON(url, function(data) { callback(data); }); } getLoc();
답변자 : Amir Fo
약속 사용
이 질문에 대한 가장 완벽한 대답은 Promise
사용하는 것입니다.
function ajax(method, url, params) { return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.onload = function() { resolve(this.responseText); }; xhr.onerror = reject; xhr.open(method, url); xhr.send(params); }); }
용법
ajax("GET", "/test", "acrive=1").then(function(result) { // Code depending on result }) .catch(function() { // An error occurred });
하지만 기다려...!
Promise 사용에 문제가 있습니다!
왜 우리만의 커스텀 Promise를 사용해야 할까요?
이전 브라우저에 오류가 있다는 것을 알 때까지 이 솔루션을 잠시 사용했습니다.
잡히지 않은 참조 오류: 약속이 정의되지 않았습니다.
그래서 ES3 용 Promise 클래스가 정의되지 않은 경우 JavaScript 컴파일러 아래에 구현하기로 결정했습니다. 메인 코드 앞에 이 코드를 추가하고 Promise를 안전하게 사용하세요!
if(typeof Promise === "undefined"){ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Promise = function () { function Promise(main) { var _this = this; _classCallCheck(this, Promise); this.value = undefined; this.callbacks = []; var resolve = function resolve(resolveValue) { _this.value = resolveValue; _this.triggerCallbacks(); }; var reject = function reject(rejectValue) { _this.value = rejectValue; _this.triggerCallbacks(); }; main(resolve, reject); } Promise.prototype.then = function then(cb) { var _this2 = this; var next = new Promise(function (resolve) { _this2.callbacks.push(function (x) { return resolve(cb(x)); }); }); return next; }; Promise.prototype.catch = function catch_(cb) { var _this2 = this; var next = new Promise(function (reject) { _this2.callbacks.push(function (x) { return reject(cb(x)); }); }); return next; }; Promise.prototype.triggerCallbacks = function triggerCallbacks() { var _this3 = this; this.callbacks.forEach(function (cb) { cb(_this3.value); }); }; return Promise; }(); }
답변자 : Pieter Jan Bonestroo
질문은 다음과 같습니다.
비동기 호출에서 응답을 어떻게 반환합니까?
다음과 같이 해석 할 수 있습니다.
비동기 코드를 동기적으로 보이게 하는 방법은 무엇입니까?
해결책은 콜백을 피하고 Promises 와 async/await 의 조합을 사용하는 것입니다.
Ajax 요청에 대한 예를 들겠습니다.
(자바스크립트로 작성해도 되지만 저는 Python으로 작성하고 Transcrypt를 사용하여 JavaScript로 컴파일하는 것을 선호합니다. 충분히 명확할 것입니다.)
$
S
사용할 수 있도록 먼저 jQuery 사용을 활성화해 보겠습니다.
__pragma__ ('alias', 'S', '$')
Promise 이 경우 Ajax 호출을 반환하는 함수를 정의합니다.
def read(url: str): deferred = S.Deferred() S.ajax({'type': "POST", 'url': url, 'data': { }, 'success': lambda d: deferred.resolve(d), 'error': lambda e: deferred.reject(e) }) return deferred.promise()
이 동기 것처럼 비동기 코드를 사용 :
async def readALot(): try: result1 = await read("url_1") result2 = await read("url_2") except Exception: console.warn("Reading a lot failed")
답변자 : Khoa Bui
물론 동기 요청, 약속 등의 접근 방식이 많이 있지만 제 경험상 콜백 방식을 사용해야 한다고 생각합니다. JavaScript의 비동기 동작은 자연스러운 현상입니다.
따라서 코드 스니펫을 약간 다르게 다시 작성할 수 있습니다.
function foo() { var result; $.ajax({ url: '...', success: function(response) { myCallback(response); } }); return result; } function myCallback(response) { // Does something. }
답변자 : Matthew Brent
코드를 던지는 대신 JavaScript가 콜백과 비동기를 처리하는 방법을 이해하는 데 핵심적인 두 가지 개념이 있습니다.
세 가지 알아야 할 사항이 있습니다. 대기열; 이벤트 루프 와 스택
광범위하고 단순한 용어로 이벤트 루프는 프로젝트 관리자와 같으며 실행하려는 모든 기능을 지속적으로 수신 대기하고 대기열과 스택 간에 통신합니다.
while (queue.waitForMessage()) { queue.processNextMessage(); }
무언가를 실행하라는 메시지를 받으면 대기열에 추가합니다. 대기열은 AJAX 요청과 같이 실행 대기 중인 항목의 목록입니다. 다음과 같이 상상해보십시오.
- foobarFunc를 사용하여 foo.com/api/bar 호출
- 무한 루프 수행 ... 등
이러한 메시지 중 하나가 실행될 때 대기열에서 메시지를 팝하고 스택을 생성합니다. 스택은 JavaScript가 메시지의 명령을 수행하기 위해 실행해야 하는 모든 것입니다. 따라서 우리의 예에서는 foobarFunc
function foobarFunc (var) { console.log(anotherFunction(var)); }
따라서 foobarFunc가 실행해야 하는 모든 것(이 경우 anotherFunction
)은 스택에 푸시됩니다. 실행된 다음 잊어버림 - 이벤트 루프는 대기열의 다음 항목으로 이동합니다(또는 메시지 수신).
여기서 중요한 것은 실행 순서입니다. 그건
WHEN은 실행될 것입니다.
AJAX를 사용하여 외부 당사자에게 전화를 걸거나 비동기 코드(예: setTimeout)를 실행할 때 JavaScript는 응답에 종속되어 계속 진행됩니다.
가장 큰 문제는 언제 응답을 받을 것인가입니다. 답은 우리가 모른다는 것입니다. 그래서 이벤트 루프는 "hey run me"라는 메시지를 기다리고 있습니다. JavaScript가 동기적으로 해당 메시지를 기다리기만 하면 앱이 멈추고 짜증날 것입니다. 따라서 JavaScript는 메시지가 대기열에 다시 추가되기를 기다리는 동안 대기열의 다음 항목을 계속 실행합니다.
이것이 비동기 기능으로 우리가 callbacks 라는 것을 사용하는 이유입니다. - 다른 함수로 전달될 때 나중에 실행될 함수 또는 핸들러입니다. Promise 는 이 비동기 동작을 보다 선형적인 방식으로 추론하는 방법으로 .then()
에 전달된 함수)을 사용합니다. Promise는 "나는 어떤 시점에 무언가를 반환할 것을 약속합니다 "라고 말하는 방식이며 콜백은 결국 반환되는 값을 처리하는 방법입니다. deffered.done
deffered.fail
및 deffered.always
(다른 것들 중에서)라는 특정 콜백을 사용합니다. 여기에서 모두 볼 수 있습니다.
그래서 당신이 해야 할 일은 전달된 데이터로 어떤 시점에서 실행하겠다고 약속된 함수를 전달하는 것입니다.
콜백은 즉시 실행되지 않지만 나중에 실행되지 않은 함수에 대한 참조를 전달하는 것이 중요합니다. 그래서
function foo(bla) { console.log(bla) }
그래서 대부분의 경우 (항상 그런 것은 아니지만) foo
아닌 foo()
이것이 의미가 있기를 바랍니다. 이와 같이 혼란스러워 보이는 상황이 발생하면 문서를 완전히 읽어서 최소한 이해하는 것이 좋습니다. 그것은 당신을 훨씬 더 나은 개발자로 만들 것입니다.
답변자 : SanjiMika
여기에서 모든 응답을 읽고 내 경험을 바탕으로 JavaScript의 비동기 프로그래밍에 대한 callback, promise and async/await
1) 콜백: 콜백 의 근본적인 이유는 이벤트에 대한 응답으로 코드를 실행하기 위한 것입니다(아래 예 참조). 우리는 매번 JavaScript에서 콜백을 사용합니다.
const body = document.getElementsByTagName('body')[0]; function callback() { console.log('Hello'); } body.addEventListener('click', callback);
그러나 아래 예제에서 중첩된 콜백을 많이 사용해야 하는 경우 코드 리팩토링에 매우 끔찍할 것입니다.
asyncCallOne(function callback1() { asyncCallTwo(function callback2() { asyncCallThree(function callback3() { ... }) }) })
2) Promise: 구문 ES6 - Promise는 콜백 지옥 문제를 해결합니다!
const myFirstPromise = new Promise((resolve, reject) => { // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed. // In this example, we use setTimeout(...) to simulate async code. // In reality, you will probably be using something like XHR request or an HTML5 API. setTimeout(() => { resolve("Success!") // Yay! Everything went well! }, 250) }) myFirstPromise .then((res) => { return res.json(); }) .then((data) => { console.log(data); }) .catch((e) => { console.log(e); });
myFirstPromise는 비동기 코드의 프로세스를 나타내는 Promise 인스턴스입니다. resolve 함수는 Promise 인스턴스가 완료되었음을 알립니다. 그 다음에는 promise 인스턴스에서 .then()(원하는 대로 .then 체인) 및 .catch()를 호출할 수 있습니다.
then — Runs a callback you pass to it when the promise has fulfilled. catch — Runs a callback you pass to it when something went wrong.
3) Async/Await: 새로운 구문 ES6 - Await는 기본적으로 Promise의 구문 설탕입니다!
Async 함수는 프라미스로 얻을 수 있는 것과 동일한 결과를 달성하기 위해 더 적은 코드를 작성할 수 있도록 하는 깨끗하고 간결한 구문을 제공합니다. Async/Await는 동기 코드와 유사하게 보이며 동기 코드는 읽고 쓰기가 훨씬 쉽습니다. Async/Await로 오류를 잡기 위해 try...catch
블록을 사용할 수 있습니다. 여기에서는 Promise 구문의 .then() 체인을 작성할 필요가 없습니다.
const getExchangeRate = async () => { try { const res = await fetch('https://getExchangeRateData'); const data = await res.json(); console.log(data); } catch (err) { console.error(err); } } getExchangeRate();
결론: 이것들은 완전히 이해해야 하는 JavaScript의 비동기 프로그래밍을 위한 세 가지 구문입니다. 따라서 가능하면 비동기 코드(대부분 XHR 요청)를 리팩토링하기 위해 "약속" 또는 "비동기/대기"를 사용해야 합니다 !
출처 : Here
출처 : http:www.stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call">