비동기 호출에서 응답을 어떻게 반환합니까?


5508

나는 기능이있다 fooAjax 요청 이 있습니다. 에서 응답을 foo어떻게 반환 합니까?

success콜백 에서 값을 반환하고 함수 내부의 로컬 변수에 응답을 할당하고 해당 변수를 반환하려고 시도했지만 실제로는 응답을 반환하지 않습니다.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.

답변:


5702

→ 다른 예제를 사용한 비동기 동작에 대한보다 일반적인 설명은 함수 내에서 변수를 수정 한 후 왜 변수가 변경되지 않습니까? 를 참조하십시오 . -비동기 코드 참조

→ 문제를 이미 이해 한 경우 아래 가능한 해결 방법으로 건너 뛰십시오.

문제

AjaxA비동기를 나타냅니다 . 즉, 요청 전송 (또는 응답 수신)은 정상적인 실행 흐름에서 제외됩니다. 귀하의 예에서 즉시 반환하고 다음 명령문은 다음과 같이 전달 된 함수 전에 실행됩니다.$.ajaxreturn 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 이상에서 사용할 수 있습니다.


ES2017 + : 약속 async/await

2017 년에 출시 된 ECMAScript 버전 에는 비동기 함수에 대한 구문 수준의 지원이 도입되었습니다 . async및 의 도움으로 await"동기식 스타일"로 비동기식을 작성할 수 있습니다. 코드는 여전히 비동기 적이지만 읽기 / 이해하기가 더 쉽습니다.

async/await약속 위에 구축 : async함수는 항상 약속을 반환합니다. await약속을 "포장 해제"하고 약속이 해결 된 값을 얻거나 약속이 거부되면 오류가 발생합니다.

중요 : 당신은 사용할 수있는 await내부 async기능. 현재 최상위 레벨 await은 아직 지원되지 않으므로 컨텍스트 를 시작하려면 비동기 IIFE ( 즉시 호출 된 함수 표현식 )를 작성해야합니다 async.

당신은에 대해 자세히 읽을 수 asyncawaitMDN에 있습니다.

다음은 위의 지연을 기반으로 구축 된 예입니다.

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

callbackfoo우리가 호출 할 때 전달하는 함수를 가리키고 단순히 전달할 수 있습니다 success. 즉, Ajax 요청이 성공 하면 콜백에 응답을 $.ajax호출 callback하고 전달합니다.result 을 정의한 방식이므로 ).

콜백에 전달하기 전에 응답을 처리 할 수도 있습니다.

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

콜백을 사용하여 코드를 작성하는 것이 생각보다 쉽습니다. 결국, 브라우저의 JavaScript는 많은 이벤트 중심 (DOM 이벤트)입니다. Ajax 응답을받는 것은 이벤트 일뿐입니다.
타사 코드로 작업해야 할 경우 문제가 발생할 수 있지만 대부분의 문제는 응용 프로그램 흐름을 생각하면 해결 될 수 있습니다.


ES2015 + : then ()으로 약속

약속 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는 본질적으로 항상 비동기 적입니다 (이 옵션을 고려하지 않는 또 하나의 이유).


74
@Pommy : jQuery를 사용하려면 포함해야합니다. docs.jquery.com/Tutorials:Getting_Started_with_jQuery를 참조하십시오 .
Felix Kling

11
해결 방법 1, 하위 jQuery를, 나는이 줄을 이해할 수 없었다 : If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(예, 내 닉이 경우 아이러니 조금 실감)
cssyphus

32
@ gibberish : 음, 그것이 어떻게 더 명확해질 수 있는지 모르겠습니다. 어떻게 foo호출되고 함수가 전달 되는지 알 수 foo(function(result) {....});있습니까 ( )? result이 함수 내에서 사용되며 Ajax 요청의 응답입니다. 이 함수를 참조하기 위해 foo의 첫 번째 매개 변수가 익명 함수 대신 호출 callback되어 할당됩니다 success. 따라서 요청이 성공 $.ajax하면 호출 callback합니다. 나는 그것을 조금 더 설명하려고 노력했다.
Felix Kling

43
이 질문에 대한 대화는 죽었으므로 개요 변경 사항을 어디에서 제안 해야할지 모르겠지만 다음과 같이 제안합니다. 2) 콜백 예제를 제거 / 병합하여보다 유연한 Deferred 접근법 만 표시하면 Javascript를 배우는 사람들이 따르기가 더 쉽다고 생각합니다.
Chris Moschini

14
@Jessi : 당신이 그 답의 일부를 오해 한 것 같아요. $.getJSONAjax 요청을 동기화 하려면 사용할 수 없습니다 . 그러나 이벤트가 요청을 동기식으로 만들고 싶지 않기 때문에 적용되지 않습니다. 응답의 앞부분에서 설명한대로 응답을 처리하기 위해 콜백이나 약속을 사용해야합니다.
Felix Kling

1071

당신이 경우 되지 코드에서 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를 사용하는 사람들을위한 훌륭한 답변을 작성했습니다.

( 새로운 fetchAPI, Angular 또는 약속을 사용하는 사람들을 위해 아래에 다른 답변을 추가했습니다. )


당신이 직면하고있는 것

이것은 다른 답변의 "문제 설명"에 대한 간략한 요약입니다.이를 읽은 후에 확실하지 않으면 읽으십시오.

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"가 표시되어야합니다. (바이올린) .

가능한 해결책

이 문제를 해결하는 방법에는 기본적으로 두 가지가 있습니다.

  1. AJAX 호출을 동 기적으로 만듭니다 (SJAX라고 함).
  2. 콜백에서 제대로 작동하도록 코드를 재구성하십시오.

1. 동기식 AJAX-하지 마라 !!

동기식 AJAX 는하지 마십시오! 펠릭스의 대답은 왜 그것이 나쁜 생각인지에 대한 설득력있는 주장을 제기합니다. 요약하면 서버가 응답을 반환하고 매우 나쁜 사용자 환경을 만들 때까지 사용자의 브라우저를 고정시킵니다. 다음은 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 시작 안내서읽으십시오 .


20
"동기 요청은 코드 실행을 차단하고 메모리와 이벤트를 유출 할 수 있습니다"동기 요청은 어떻게 메모리를 유출 할 수 있습니까?
Matthew G

10
@ MatthewG 나는 이 질문 에 현상금을 추가했으며 , 내가 낚시질 할 수있는 것을 볼 수 있습니다. 그동안 답변에서 따옴표를 제거하고 있습니다.
Benjamin Gruenbaum

17
참고로 XHR 2를 사용하면 onload핸들러 만 사용할 수 있습니다. 핸들러 readyState는 is 일 때만 발생 4합니다. 물론 IE8에서는 지원되지 않습니다. (iirc, 확인이 필요할 수 있습니다.)
Florian

9
익명 함수를 콜백으로 전달하는 방법에 대한 설명은 유효하지만 오해의 소지가 있습니다. 예제 var bar = foo (); 제안 된 foo (functim () {}); 반면 변수를 정의하도록 요청하고 있습니다. 바를 정의하지 않음
Robbie Averill

396

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

보다시피 :

  1. 나열된 다른 모든 기능보다 짧습니다.
  2. 콜백이 직접 설정되므로 불필요한 추가 클로저가 없습니다.
  3. 새로운 onload를 사용하므로 readystate && status를 확인할 필요가 없습니다.
  4. 내가 기억하지 못하는 다른 상황이 있는데 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 고급 기능 확인

모든 * modern 브라우저를 지원했습니다. XMLHttpRequest 2가 존재하기 때문에이 접근법을 사용하고 있음을 확인할 수 있습니다. 내가 사용하는 모든 브라우저에서 문제가 발생하지 않았습니다.

onreadystatechange는 상태 2에서 헤더를 가져 오려는 경우에만 유용합니다.

XMLHttpRequestonload / 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)
}

다시 말하지만 ... 매우 짧은 기능이지만 가져오고 게시합니다.

사용 예 :

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에 오류가 발생하는 잘못된 URL을 작성하는 것입니다.

오류 처리기는 사용자 정의 헤더를 설정하고 responseType을 blob 배열 버퍼 또는 기타로 설정하는 경우 유용합니다.

메소드로 'POSTAPAPAP'을 전달하더라도 오류가 발생하지 않습니다.

'fdggdgilfdghfldj'를 formdata로 전달하더라도 오류가 발생하지 않습니다.

첫 번째 경우 오류는 displayAjax()아래에 this.statusText있습니다 Method not Allowed.

두 번째 경우에는 간단하게 작동합니다. 올바른 게시물 데이터를 전달했는지 서버 측에서 확인해야합니다.

도메인 간 허용되지 않으면 오류가 자동으로 발생합니다.

오류 응답에는 오류 코드가 없습니다.

this.type오류로 설정된 것만 있습니다 .

오류를 완전히 제어 할 수없는 경우 왜 오류 처리기를 추가합니까? 콜백 함수에서 대부분의 오류가이 안에 반환됩니다 displayAjax().

따라서 URL을 올바르게 복사하여 붙여 넣을 수 있으면 오류를 확인할 필요가 없습니다. ;)

추신 : 첫 번째 테스트로 x ( 'x', displayAjax) ...를 작성했으며 완전히 응답했습니다 ... ??? 그래서 HTML이있는 폴더를 확인했는데 'x.xml'이라는 파일이있었습니다. 따라서 파일의 확장자를 잊어 버린 경우에도 XMLHttpRequest 2 WILL FIND IT . 나는 LOL했다


동기식 파일 읽기

하지마

잠시 동안 브라우저를 차단하려면 멋진 큰 .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 페이지에 통합하는 첫 번째 기능 중 하나는이 답변의 첫 번째 Ajax 함수이며 GET 만 사용합니다 ...

그러나 XMLHttpRequest 2로 많은 작업을 수행 할 수 있습니다.

다운로드 관리자 (이력서, 파일 판독기, 파일 시스템과 함께 양쪽에 범위 사용), 캔버스를 사용하는 다양한 이미지 리사이 저 변환기, base64image 등으로 웹 SQL 데이터베이스 채우기 등 ... 그러나이 경우에만 해당 기능을 만들어야합니다 목적 ... 때로는 BLOB, 배열 버퍼가 필요하며 헤더를 설정하고 mimetype을 재정의 할 수 있으며 훨씬 더 많이 있습니다 ...

그러나 여기서 질문은 Ajax 응답을 반환하는 방법입니다 ... (나는 쉬운 방법을 추가했습니다.)


15
이 답변은 훌륭하지만 (우리는 모두 XHR2를 좋아 하고 파일 데이터 및 멀티 파트 데이터를 게시하는 것이 정말 대단합니다)-이것은 XHR을 JavaScript로 게시하기위한 구문 설탕을 보여줍니다-이것을 블로그 게시물에 넣기를 원할 수도 있습니다 심지어 라이브러리 (하지 않도록 이름에 대해 x, ajax또는 xhr:) 더 좋은 수 있습니다). AJAX 호출에서 응답을 반환하는 방법을 알 수 없습니다. (누군가 여전히 var res = x("url")왜 작동하지 않는지 이해할 수 없었습니다.)). 참고로- c사용자가 메소드 error등에서
접속할

25
2.ajax is meant to be async.. so NO var res=x('url')..
이것이이

3
첫 번째 줄에 어떤 값을 덮어 쓰면 함수에 'c'매개 변수가 있습니까? 내가 뭔가를 놓치고 있습니까?
Brian H.

2
"var"를 여러 번 쓰지 않도록 자리 표시 자로 매개 변수를 사용할 수 있습니다.
cocco

11
@cocco 그래서 몇 번의 키 입력을 줄이기 위해 SO 답변 에 오도하고 읽을 수없는 코드를 작성 했습니까? 그렇게하지 마십시오.
스톤

316

약속을 사용하고 있다면이 대답은 당신을위한 것입니다.

이는 AngularJS, jQuery (지연된), 기본 XHR 대체 (페치), EmberJS, BackboneJS의 저장 또는 약속을 반환하는 모든 노드 라이브러리를 의미합니다.

코드는 다음과 같이 표시되어야합니다.

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은 jQuery를 AJAX 콜백과 함께 사용하는 사람들을 위해 훌륭한 답변을 작성했습니다. 네이티브 XHR에 대한 답변이 있습니다. 이 답변은 프론트 엔드 또는 백엔드에서 일반적인 약속 사용에 대한 것입니다.


핵심 이슈

NodeJS / io.js를 사용하는 브라우저 및 서버의 JavaScript 동시성 모델은 비동기식 이며 반응성이 있습니다.

당신은 반환 약속이의하는 메서드를 호출 할 때마다 then처리기가되어 항상 이다 -로 비동기 적으로 실행 A의 아닌 그 아래의 코드 .then처리기를.

이 방법 당신은 반환 할 때 아직 실행하지 못했습니다 정의한 핸들러를. 이는 반환하는 값이 올바른 시간으로 설정되지 않았 음을 의미합니다.datathen

다음은이 문제에 대한 간단한 비유입니다.

    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이다 undefined이후 data = 5부분이 아직 실행하지 않았다. 아마도 1 초 안에 실행될 것이지만 그때까지는 반환 값과 관련이 없습니다.

작업이 아직 발생하지 않았기 때문에 (AJAX, 서버 호출, IO, 타이머) 요청이 코드에 해당 값이 무엇인지 알려주기 전에 값을 반환합니다.

이 문제에 대한 한 가지 가능한 해결책은 재 반응 적으로 코드를 작성 하여 계산이 완료되었을 때 수행 할 작업을 프로그램에 알려주는 것입니다. 약속은 본질적으로 시간적 (시간에 민감 함)이어서 적극적으로 가능하게합니다.

약속에 대한 빠른 요약

약속은 시간이 지남에 따른 가치 입니다. 약속에는 상태가 있으며 값없이 보류중인 것으로 시작하여 다음과 같이 해결할 수 있습니다.

  • 계산이 성공적으로 완료 되었음을 의미합니다.
  • 거부 계산이 실패했음을 의미한다.

약속은 한 번만 상태를 변경할 수 있으며 그 후에는 항상 같은 상태를 영원히 유지합니다. then처리기를 연결 하여 값을 추출하고 오류를 처리 할 수 ​​있습니다. then처리기 는 호출 체인 을 허용 합니다. 약속은이 를 반환하는 API를 사용하여 만들어집니다 . 예를 들어, 최신 AJAX 교체 fetch또는 jQuery의 $.get반환 약속.

우리가 호출 할 때 .then약속과에 반환 그것에서 무엇인가 - 우리의 약속을받을 처리 된 값을 . 우리가 다른 약속을 돌려 주면 놀라운 일을하게되지만 말을 붙잡아 봅시다.

약속으로

약속으로 위의 문제를 어떻게 해결할 수 있는지 봅시다. 먼저 지연 함수를 만들기 위해 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, 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/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

반복 가능한 시퀀스 에서 반복자 를 리턴하는 함수입니다 1,2,3,3,3,3,..... 이것은 그 자체로 흥미롭고 많은 가능성을위한 여지를 열어 주지만 특별한 흥미로운 사례가 하나 있습니다.

우리가 생성하는 시퀀스가 ​​숫자가 아닌 일련의 액션 인 경우-액션이 발생할 때마다 함수를 일시 중지하고 함수를 다시 시작하기 전에 기다릴 수 있습니다. 따라서 일련의 숫자 대신 미래 가치 의 순서, 즉 약속이 필요합니다.

다소 까다 롭지 만 강력한 트릭으로 비동기 코드를 동기 방식으로 작성할 수 있습니다. 이 작업을 수행하는 몇 가지 "실행자"가 있습니다. 하나를 작성하는 것은 몇 줄의 코드이지만이 답변의 범위를 벗어납니다. Promise.coroutine여기서는 Bluebird를 사용 하지만 coor 와 같은 다른 래퍼가 있습니다 Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

이 메소드는 다른 코 루틴에서 사용할 수있는 promise 자체를 반환합니다. 예를 들면 다음과 같습니다.

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, 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 done
});
main();

ES2016 (ES7)

ES7에서는 이것이 표준화되어 있으며, 현재 몇 가지 제안이 있지만 모든 제안에서 await약속 할 수 있습니다 . 위의 ES6 제안에 대해 asyncand await키워드 를 추가하여 "설탕"(더 간단한 구문)입니다 . 위의 예제를 작성하십시오.

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
}

그것은 여전히 ​​똑같은 약속을 반환합니다 :)


이것이 정답입니다. async / await +1 (아직 안 return await data.json();
Lewis Donovan

247

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

제출 핸들러에서 아무것도 리턴하지 않으면 아무 것도 수행되지 않습니다. 대신 데이터를 전달하거나 성공 함수 내에서 직접 원하는 데이터를 수행해야합니다.


13
이 답변은 완전히 의미 론적입니다 ... 성공 방법은 콜백 내의 콜백입니다. 당신은 가질 수 있고 success: handleData작동합니다.
Jacques ジ ャ ク

5
"handleData"외부에서 "responseData"를 반환하려면 어떻게해야합니까 ... :) ... 어떻게 하시겠습니까? ... 간단한 반환 아약스의 "성공"콜백으로 돌아갑니다 원인이 ... 아니라 외부 "handleData"의 ...
pesho 흐리스토프

@Jacques & @pesho hristov이 시점을 놓쳤다. 제출 핸들러는 success메소드 가 아니며의 범위입니다 $.ajax.
travnik

@travnik 나는 그것을 놓치지 않았다. handleData의 내용을 가져 와서 성공 방법으로 넣으면 정확히 동일하게 작동합니다.
Jacques ジ ャ ク

234

가장 간단한 해결책은 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 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);    
}); 

3
누가 부정 투표했는지 모르겠습니다. 그러나 이것은 실제로이 접근법을 사용하여 전체 응용 프로그램을 만들었던 해결 방법입니다. jquery.ajax는 데이터를 반환하지 않으므로 위의 접근 방식을 사용하는 것이 좋습니다. 그것이 틀렸다면 설명하고 더 나은 방법을 제안하십시오.
Hemant Bavle

11
죄송합니다. 댓글을 남겼습니다 (보통 그렇습니다!). 나는 그것을 하향 투표했다. 다운 보트는 사실의 정확성 또는 부족을 나타내지 않으며 상황 또는 유용성의 유용성을 나타냅니다. 필자가 이미 이것을 훨씬 더 자세히 설명하는 Felix의 답변이 유용하다고 생각하지 않습니다. 참고로, JSON 인 경우 응답을 왜 문자열 화합니까?
Benjamin Gruenbaum

5
ok .. @ Benjamin JSON 객체를 문자열로 변환하기 위해 stringify를 사용했습니다. 당신의 요점을 명확하게 해주셔서 감사합니다. 더 정교한 답변을 게시하는 것을 명심하십시오.
Hemant Bavle

그리고 "successCallback"외부에서 "responseObj"를 반환하려면 어떻게해야합니까 ... :) ... 어떻게 하시겠습니까? ... 간단한 반환 아약스의 "성공"콜백으로 돌아갑니다 원인이 ... 아니라 외부 "successCallback"의 ...
pesho 흐리스토프

221

나는 끔찍한 손으로 그린 ​​만화로 대답 할 것입니다. 두 번째 이미지는 이유입니다 result입니다 undefined코드 예제에서.

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


32
그림 가치가 천 단어입니다 , 사람 A가 - 다시, 자신의 차를 해결하기 위해의 사람 B의 세부 사항을 물어 사람 B - 자동차 응답이 수신 될 때, 세부 사항을 고정하기위한 서버에서 응답을 아약스 통화 대기를 만든다, 아약스 성공 기능은 사람 호출 B 함수가 응답을 인수로 전달하면 Person A가 응답을받습니다.
shaijut

10
개념을 설명하기 위해 각 이미지에 코드 줄을 추가하면 좋을 것입니다.
Hassan Baig

1
한편, 차를 가진 사람은 길가에 붙어 있습니다. 그는 필요로 차를 계속하기 전에 고정됩니다. 그는 이제 길가에서 기다리고 있습니다. 전화를 걸고 상태 변경을 기다리는 것이지만 정비공은 그렇게하지 않을 것입니다 ... 정비공은 자신의 일을 계속해야한다고 말했지만 전화를 끊기 만하면됩니다. 정비공은 최대한 빨리 다시 전화하겠다고 약속했습니다. 약 4 시간 후, 그 사람은 포기하고 우버에게 전화합니다. -시간 초과 예.
barrypicker

@ barrypicker : D 화려한!
Johannes Fahrenkrug

159

앵귤러 1

AngularJS 를 사용하는 사람들 은를 사용하여이 상황을 처리 할 수 ​​있습니다 Promises.

여기에 ,

약속을 사용하여 비동기 함수를 제거하고 여러 함수를 함께 연결할 수 있습니다.

여기서도 좋은 설명을 찾을 수 있습니다 .

아래에 언급 된 문서 에서 예를 찾을 수 있습니다.

  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.

Angular2 이상

에서 Angular2다음과 같은 예를 살펴 그러나 그와 권장 사용에 ObservablesAngular2.

 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를 지원하지 않습니다. Typescript 를 사용하려면 플러그인이 필요할 수 있습니다.

또한 여기에 약속 사양이 정의되어 있습니다.


15
이것은 약속이 어떻게이 문제를 해결하는지 설명하지 않습니다.
Benjamin Gruenbaum

4
jQuery와 fetch 메소드는 모두 약속을 반환합니다. 답을 수정 해 보시기 바랍니다. jQuery는 완전히 동일하지 않지만 (그렇지만 catch는 그렇지 않습니다).
Tracker1

153

여기에있는 대부분의 답변은 단일 비동기 작업이있을 때 유용한 제안을 제공하지만 때로는 배열 또는 다른 목록과 같은 구조의 항목에 대해 비동기 작업을 수행해야 할 때 나타납니다 . 유혹은 이것을하는 것입니다 :

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

예:

작동하지 않는 이유 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); // E.g., using the results
        }
    });
});

예:

(우리는을 사용하지 않고 expecting그냥 사용할 수 results.length === theArray.length는 있지만 theArray통화가 진행되는 동안 변경 될 수있는 가능성에 우리를 열어 둡니다 ...)

비동기 호출이 시작된 순서대로 완료 될 필요가 없기 때문에 결과가 순서에 맞지 않더라도 indexfrom forEach을 사용하여 결과를 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);
});

예:

또는 다음 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하면 오류가 발생했을 때 약속을 거부하는 데 사용 됩니다.)

예:

(또는 대안 doSomethingAsync으로 약속을 반환하는 래퍼를 만든 다음 아래를 수행 할 수 있습니다 ...)

경우 doSomethingAsync당신에게주는 약속을 , 당신은 사용할 수 있습니다 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세 개의 인수로 콜백을 호출하지만 대부분의 사람들은 처음으로 만 사용합니다).

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

예:

참고 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;있지만 다음 예 중 일부에서는 색인이 없습니다. 쓰다.)

예:

(또는 다시 한 번 doSomethingAsync약속을 드리고 아래를 수행 하는 래퍼를 작성 하십시오 ...)

경우 doSomethingAsync당신에게 약속을 준다 당신이 (아마도 같은 transpiler과 ES2017 + 구문을 사용할 수있는 경우, 바벨 ), 당신은 사용할 수있는 async기능을 함께 for-of하고 await:

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

예:

ES2017 + 구문 (아직)을 사용할 수없는 경우 "Promise reduce"패턴 의 변형을 사용할 수 있습니다 (결과를 다음으로 전달하지 않기 때문에 일반적인 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);
});

예:

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

예:


1
if (--expecting === 0)코드 의 일부가 어떻게 작동 하는지 설명해 주 시겠습니까? 귀하의 솔루션의 콜백 버전이 저에게 효과적이며, 그 진술로 귀하가 완료 된 응답 수를 확인하는 방법을 이해하지 못합니다. 내 지식이 부족하다는 것을 이해하십시오. 수표를 작성할 수있는 다른 방법이 있습니까?
Sarah

@Sarah : expecting의 값으로 시작합니다.이 값은 array.length요청 횟수입니다. 요청이 모두 시작될 때까지 콜백이 호출되지 않습니다. 콜백에서 다음을 if (--expecting === 0)수행합니다. 1. 감소 expecting(응답을 받았으므로 응답이 더 적을 것으로 예상 됨) 감소 후의 값 이 0 (더 이상 응답을 기대하지 않음) 인 경우 끝난!
TJ Crowder

1
@PatrickRoberts-감사합니다 !! 예, 복사하여 붙여 넣기 오류입니다.이 예제에서는 두 번째 인수가 완전히 무시되었습니다 results. :-) 고쳤다.
TJ Crowder

111

이 예를 살펴보십시오.

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). 따라서 $ 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);
  });
})();

107

이것은 많은 새로운 JavaScript 프레임 워크에서 사용 되는 두 가지 데이터 바인딩 또는 저장소 개념 이 당신에게 잘 작동 하는 장소 중 하나입니다 ...

따라서 Angular, React 또는 두 가지 방법으로 데이터 바인딩 또는 저장 개념 을 수행하는 다른 프레임 워크를 사용하는 경우이 문제는 간단하게 해결되므로 쉽게 말하면 결과가 undefined첫 번째 단계이므로 결과 result = undefined를 받기 전에 결과를 얻 자마자 업데이트되고 Ajax 호출의 응답에 새로운 값이 할당됩니다 ...

그러나이 질문에서 요청한 것처럼 순수 자바 스크립트 또는 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(); //call happens here
});

이 비동기 작업을 수행하는 새로운 방법 인 약속관찰 가능 항목 에 대한 자세한 내용은 연구하십시오 .


이것은 전체 범위에서 괜찮지 만 일부 모듈 컨텍스트에서는 아마도 콜백에 대한 올바른 컨텍스트를 보장하려고합니다.$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims

8
React가 단방향 데이터 바인딩이기 때문에 이것은 실제로 올바르지 않습니다
Matthew Brent

@MatthewBrent 당신은 틀린 것이 아니지만, 옳지 않습니다. React props는 객체이며 변경되면 응용 프로그램 전체에서 변경되지만 React 개발자가 권장하는 방식은 아닙니다 ...
Alireza

98

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 초 후에 함수를 실행하여 'wohoo'를 반환합니다. 그러나 보시다시피 bar에 할당 된 값은 foo ()에서 즉시 반환되는 것입니다 undefined.

그렇다면이 문제를 어떻게 해결해야합니까?

함수에 PROMISE 를 요청합시다 . 약속은 실제로 그것이 의미하는 바에 관한 것입니다. 즉,이 기능은 미래에 얻을 수있는 모든 출력을 제공한다는 것을 의미합니다. 위의 작은 문제에 대해 실제로 살펴 보겠습니다.

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when 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'
});

따라서 요약은-아약스 기반 호출 등과 같은 비동기 함수를 다루기 위해 resolve값에 대한 약속을 사용할 수 있습니다 (반환하려는 값). 따라서, 한마디로 당신은 해결 대신 값을 반환 비동기 기능에.

업데이트 (비동기 / 대기 약속)

then/catch약속을 다루는 데 사용 하는 것 외에도 하나의 접근 방식이 있습니다. 아이디어는 비동기 함수인식 한 후 다음 코드 줄로 이동하기 전에 약속 이 해결 될 때까지 기다리는 것입니다 . 그것은 여전히 promises중요한 부분이지만, 다른 구문 방식입니다. 더 명확하게하기 위해 아래 비교를 찾을 수 있습니다.

그런 다음 / 캐치 버전 :

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

이것은 여전히 ​​약속 또는 비동기 / 대기에서 값을 반환하는 가장 좋은 방법으로 간주됩니까?
edwardsmarkf

3
@ edwardsmarkf 개인적으로 나는 최선의 방법이 있다고 생각하지 않습니다. then / catch, async / await 및 내 코드의 비동기 부분 생성기를 사용하여 promise를 사용합니다. 사용 상황에 따라 크게 다릅니다.
Anish K.

96

비동기 함수에서 값을 반환하는 다른 방법은 비동기 함수의 결과를 저장할 개체를 전달하는 것입니다.

다음은 동일한 예입니다.

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비동기 작업 중에 값을 저장하는 객체. 이를 통해 비동기 작업 후에도 결과를 사용할 수 있습니다.

나는이 접근법을 많이 사용합니다. 연속 모듈을 통해 결과를 다시 배선하는 경우이 방법이 얼마나 잘 작동하는지 알고 싶습니다.


9
여기에 객체를 사용하는 데 특별한 것은 없습니다. 응답을 직접 할당 한 경우에도 효과가 있습니다 result. 비동기 함수가 완료된 변수를 읽고 있기 때문에 작동합니다 .
Felix Kling

85

약속과 콜백은 많은 상황에서 잘 작동하지만 다음과 같은 것을 표현하는 것은 고통 스럽습니다.

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
}

여기서 프로젝트를 확인할 수 있습니다 .


1
@recurf-내 프로젝트가 아닙니다. 이슈 트래커를 사용해보십시오.
rohithpr

1
이것은 발전기 기능과 비슷합니까? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… *
Emanegux

1
이것은 여전히 ​​관련이 있습니까?
Aluan Haddad

async-await최신 버전의 노드를 사용중인 경우 사용할 수 있습니다 . 누군가 이전 버전을 사용하고 있다면이 방법을 사용할 수 있습니다.
rohithpr

83

내가 작성한 다음 예제는 방법을 보여줍니다.

  • 비동기 HTTP 호출을 처리하십시오.
  • 각 API 호출의 응답을 기다립니다.
  • 약속 패턴을 사용하십시오 .
  • Promise.all 패턴을 사용 하여 여러 HTTP 호출에 참여 하십시오 .

이 작업 예제는 독립적입니다. window XMLHttpRequest객체를 사용하여 호출 하는 간단한 요청 객체를 정의 합니다. 많은 약속이 완료되기를 기다리는 간단한 함수를 정의합니다.

문맥. 예제는 지정된 쿼리 문자열 세트에 대한 객체 를 검색하기 위해 Spotify Web API 엔드 포인트 playlist를 쿼리하는 것입니다.

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

각 항목에 대해 새로운 Promise는 블록을 실행 ExecutionBlock하고, 결과를 구문 분석하고, 결과 배열을 기반으로 새로운 약속 세트를 예약합니다. 즉, Spotify user객체 목록이며 ExecutionProfileBlock비동기식으로 새 HTTP 호출을 실행합니다 .

그런 다음 중첩 된 Promise 구조를 볼 수 있습니다.이를 통해 여러 개의 완전히 비동기식 중첩 된 HTTP 호출을 생성하고를 통해 각 호출의 하위 집합에서 결과에 참여할 수 있습니다 Promise.all.

참고 최근 Spotify searchAPI를 사용하려면 요청 헤더에 액세스 토큰을 지정해야합니다.

-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" />

나는 광범위하게이 솔루션을 논의한 여기 .


80

짧은 대답은 다음과 같이 콜백을 구현해야한다는 것입니다.

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});

78

2017 년 답변 : 이제 모든 현재 브라우저 및 노드에서 원하는 것을 정확하게 수행 할 수 있습니다

이것은 매우 간단합니다.

  • 약속을 반환
  • 'await'을 사용하면 JavaScript가 HTTP 응답과 같은 값으로 해결 될 약속을 기다리도록 JavaScript에 지시합니다.
  • 부모 함수에 '비동기' 키워드 추가

작동하는 코드 버전은 다음과 같습니다.

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

대기는 모든 현재 브라우저 및 노드 8에서 지원됩니다.


7
불행히도 이것은 약속을 반환하는 함수에서만 작동합니다. 예를 들어 콜백을 사용하는 Node.js API에서는 작동하지 않습니다. 모든 사람이 "현재 브라우저"를 사용하는 것은 아니기 때문에 Babel 없이는 사용하지 않는 것이 좋습니다.
Michał Perłakowski

2
@ MichałPerłakowski 노드 8에는 node.js API 반환 약속을 만드는 데 사용할 수있는 nodejs.org/api/util.html#util_util_promisify_original 이 포함 되어 있습니다. 비 최신 브라우저를 지원할 시간과 비용이 있는지 여부는 상황에 따라 다릅니다.
mikemaccana

IE 11은 여전히 ​​슬프게도 2018 년 현재 브라우저이며 지원하지 않습니다await/async
Juan Mendes

IE11은 현재 브라우저가 아닙니다. 5 년 전에 출시되었으며, 캐니 우스에 따르면 전 세계 시장 점유율은 2.5 %이며, 현재의 모든 기술을 무시하기 위해 예산을 두 배로 늘리지 않는 한 대부분의 사람들에게 가치가 없습니다.
mikemaccana

76

JS는 단일 스레드입니다.

브라우저는 세 부분으로 나눌 수 있습니다.

1) 이벤트 루프

2) 웹 API

3) 이벤트 큐

이벤트 루프는 무한 루프의 일종으로 영원히 실행됩니다. 이벤트 큐는 모든 이벤트가 일부 이벤트 (예 : 클릭)에서 푸시되는 곳입니다. 이것은 큐에서 수행되고 이벤트 루프에 넣어이 기능을 실행하고 자체 준비합니다. 이것은 첫 번째 함수가 실행 된 후 다음 하나에 대한 것입니다. 즉, 하나의 함수 실행은 큐에있는 함수가 이벤트 루프에서 실행될 때까지 시작되지 않습니다.

이제 하나는 서버에서 데이터를 가져 오기위한 것이고 다른 하나는 해당 데이터를 이용하는 것입니다. 우리는 먼저 queueRequest에서 serverRequest () 함수를 누른 다음 utiliseData () 함수를 푸시합니다. serverRequest 함수는 이벤트 루프로 이동하여 서버에서 데이터를 가져 오는 데 걸리는 시간을 알지 못하므로 서버를 호출 하므로이 프로세스에는 시간이 걸리므로 이벤트 루프가 바빠서 페이지가 중단됩니다. API가 역할을 수행하면 이벤트 루프 에서이 함수를 가져 와서 서버에서 이벤트 루프를 자유롭게 처리하여 큐에서 다음 함수를 실행할 수 있습니다. 큐의 다음 함수는 루프에 들어가는 utiliseData ()이지만 사용할 수있는 데이터가 없기 때문에 대기열이 끝날 때까지 다음 함수의 낭비와 실행이 계속됩니다 (Async 호출이라고합니다. 즉 데이터를 얻을 때까지 다른 작업을 수행 할 수 있습니다)

serverRequest () 함수가 코드에 return 문을 가지고 있다고 가정 해 봅시다. 서버 웹 API에서 데이터를 가져올 때 큐의 끝에서 큐로 푸시합니다. 대기열의 끝에서 푸시되기 때문에 대기열 에이 데이터를 활용하는 기능이 없으므로 데이터를 활용할 수 없습니다. 따라서 비동기 호출에서 무언가를 반환 할 수 없습니다.

따라서 이것에 대한 해결책은 콜백 또는 약속 입니다.

여기에 대한 답변 중 하나의 이미지, 콜백 사용을 올바르게 설명합니다 ... 우리는 함수 (서버에서 반환 된 데이터를 사용하는 함수)를 함수 호출 서버에 제공합니다.

콜백

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

Javscript.info 콜백


68

이 사용자 지정 라이브러리 (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)
});

67

또 다른 해결책은 순차적 실행 프로그램 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는 모든 연산자와 표현식을 단계별로 평가하여 일부 느린 기능의 결과가 준비되지 않은 경우 실행을 일시 중지합니다.

여기에 더 많은 예제가 있습니다 : https://github.com/amaksr/nsynjs/tree/master/examples


2
이것은 흥미 롭다. 다른 언어로 비동기 호출을 코딩하는 방법이 마음에 듭니다. 그러나 기술적으로 실제 JavaScript가 아닙니까?
J Morris

41

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

39

다음은 비동기 요청을 처리하는 몇 가지 방법입니다.

  1. 브라우저 약속 개체
  2. Q -JavaScript를위한 약속 라이브러리
  3. A + Promises.js
  4. jQuery 연기
  5. XMLHttpRequest API
  6. 콜백 개념 사용-첫 번째 답변의 구현으로

예 : 여러 요청에 대해 작동하도록 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();


38

우리는 우리가 "시간"이라고 부르는 차원을 따라 진보하는 것처럼 보이는 우주에서 자신을 발견합니다. 우리는 시간이 무엇인지 실제로 이해하지 못하지만, 우리는 "과거", "현재", "미래", "전", "후"와 같이 추론하고 그것에 대해 이야기 할 수있는 추상화와 어휘를 개발했습니다.

점점 더 많은 컴퓨터 시스템은 시간을 중요한 차원으로 가지고 있습니다. 미래에는 어떤 일들이 일어나도록 설정되어 있습니다. 그런 다음 첫 번째 일이 결국 발생한 후에 다른 일이 발생해야합니다. 이것이 "비동기 성"이라는 기본 개념입니다. 점점 더 네트워크화되는 세상에서 가장 일반적인 비 동시성 사례는 일부 원격 시스템이 일부 요청에 응답하기를 기다리는 것입니다.

예를 고려하십시오. 우유 배달원에게 전화해서 우유를 주문하십시오. 그것이 올 때, 당신은 당신의 커피에 넣기를 원합니다. 우유가 아직 없기 때문에 커피에 우유를 넣을 수 없습니다. 커피를 넣으려면 커피가 올 때까지 기다려야합니다. 즉, 다음은 작동하지 않습니다.

var milk = order_milk();
put_in_coffee(milk);

JS는 필요가 있음을 알 수있는 방법이 없기 때문에 대기 위해 order_milk자신을 실행하기 전에 마무리를 put_in_coffee. 즉, 그 모르는 order_milk입니다 비동기 미래의 어느 시간까지 우유를 초래하지 않을 --is 뭔가. JS 및 기타 선언적 언어는 기다리지 않고 한 명령문을 차례로 실행합니다.

JS가 전달할 수있는 일급 객체로 함수를 지원한다는 사실을 이용하여이 문제에 대한 고전적인 JS 접근 방식은 함수를 비동기 요청에 매개 변수로 전달하여 완료되면 호출합니다. 미래의 언젠가는 이것이 "콜백"접근법입니다. 다음과 같이 보입니다 :

order_milk(put_in_coffee);

order_milk우유가 튀어 나와서 우유를 주문한 다음 도착했을 때만 우유를 공급합니다 put_in_coffee.

이 콜백 접근법의 문제점은이 함수가 결과를보고하는 함수의 일반적인 의미를 오염시키는 것입니다 return. 대신 함수는 매개 변수로 제공된 콜백을 호출하여 결과를보고해서는 안됩니다. 또한이 방법은 더 긴 일련의 이벤트를 처리 할 때 다루기 어려워 질 수 있습니다. 예를 들어, 우유가 커피에 담길 때까지 기다렸다가 세 번째 단계, 즉 커피를 마신다고 가정 해 봅시다. 결국 다음과 같이 작성해야합니다.

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

put_in_coffee우유를 넣을 우유와 우유를 넣은 후에 drink_coffee실행할 행동 ( ) 을 모두 전달합니다 . 이러한 코드를 작성하고 읽고 디버깅하기가 어렵습니다.

이 경우 질문의 코드를 다음과 같이 다시 작성할 수 있습니다.

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

약속을 입력

이것은 "약속"이라는 개념에 대한 동기였습니다. 이는 일종의 미래 또는 비동기 결과 를 나타내는 특정 유형의 가치입니다 . 이미 일어난 일이나 미래에 일어날 일이거나 전혀 일어날 일이없는 것을 나타낼 수 있습니다. 약속에는 then약속이 나타내는 결과가 실현되었을 때 실행될 조치를 전달 하는 단일 메소드 가 있습니다.

우리 우유와 커피의 경우, 우리는 설계 order_milk후, 도착 우유에 대한 약속을 반환 지정하는 put_in_coffeeA와 then다음과 같이 조치 :

order_milk() . then(put_in_coffee)

이것의 한 가지 장점은이 문자열들을 함께 묶어 향후 발생 순서 ( "체인")를 만들 수 있다는 것입니다.

order_milk() . then(put_in_coffee) . then(drink_coffee)

특정 문제에 대한 약속을 적용합시다. 우리는 요청 로직을 함수로 감싸서 약속을 반환합니다.

function get_data() {
  return $.ajax('/foo.json');
}

실제로, 우리가 한 일은 return에 대한 호출에 추가 됩니다 $.ajax. 이것은 jQuery가 $.ajax이미 일종의 약속 같은 것을 반환 하기 때문에 작동합니다 . (실제로 세부 정보를 얻지 않고이 약속을 반환하기 위해이 호출을 감싸거나 다른 방법을 사용 $.ajax하는 것이 좋습니다.) 이제 파일을로드하고 완료되기를 기다리는 경우 우리는 단순히 할 수 있습니다

get_data() . then(do_something)

예를 들어

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

약속을 사용할 때 많은 함수를로 전달 then하므로 더 컴팩트 한 ES6 스타일 화살표 함수를 사용하는 것이 도움이됩니다.

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

그만큼 async키워드

그러나 동 기적 인 경우 코드를 작성하는 방법과 비동기 인 경우 상당히 다른 방법으로 코드를 작성 해야하는 것에 대해 여전히 모호한 불만이 있습니다. 동기식으로

a();
b();

그러나 a비동기 인 경우 약속을 작성해야합니다.

a() . then(b);

위에서 우리는 "JS는 두 번째 전화를하기 전에 첫 번째 전화가 끝나기 를 기다릴 필요가 없다"고 말했다 . JS에게 말할 방법 있다면 좋지 않습니까? 더 - 그것은이 있음을 밝혀 await기능 특수한 유형의 내부에 사용되는 키워드는,에 "비동기"함수를 호출. 이 기능은 곧 출시 될 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);
}

37

짧은 대답 : 함수는을 반환 한 후 호출이 비동기 적으로 실행 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.


14
이것이 효과가 있지만 전역 변수에 할당하는 것보다 낫지는 않습니다.
Felix Kling

36

성공 callback()안에 기능을 사용하십시오 foo(). 이런 식으로 시도하십시오. 간단하고 이해하기 쉽습니다.  

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

29

문제는 :

비동기 호출에서 응답을 어떻게 반환합니까?

다음과 같이 해석 될 수 있습니다.

비동기 코드를 동 기적으로 보이게 만드는 방법은 무엇입니까?

해결책은 콜백을 피하고 약속비동기 / 대기 의 조합을 사용하는 것입니다. .

Ajax 요청에 대한 예제를 제공하고 싶습니다.

(Javascript로 작성할 수는 있지만 Python으로 작성하고 Transcrypt를 사용하여 Javascript로 컴파일하는 것을 선호합니다. . 충분히 명확합니다.)

먼저 JQuery 사용을 활성화 $하여 S다음 과 같이 사용할 수 있습니다 .

__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")

29

약속 사용

이 질문에 대한 가장 완벽한 대답은를 사용하는 것 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
});

하지만 기다려...!

약속 사용에 문제가 있습니다!

우리는 왜 우리 자신의 맞춤형 약속을 사용해야합니까?

오래된 브라우저에 오류가 있음을 알 때 까지이 솔루션을 잠시 사용했습니다.

Uncaught ReferenceError: Promise is not defined

그래서 ES3에 대한 자체 Promise 클래스를 정의하지 않은 경우 js 컴파일러 아래 로 구현하기로 결정했습니다 . 기본 코드 앞에이 코드를 추가 한 다음 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;
    }();
}

28

물론 동기 요청, 약속과 같은 많은 접근법이 있지만 내 경험으로는 콜백 접근법을 사용해야한다고 생각합니다. Javascript의 비동기 동작은 자연 스럽습니다. 따라서 코드 스 니펫을 약간 다르게 다시 작성할 수 있습니다.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}

5
콜백이나 JavaScript에 대해 본질적으로 비동기적인 것은 없습니다.
Aluan Haddad

19

코드를 던지기보다는 JS가 콜백 및 비 동시성을 처리하는 방법을 이해하는 데 중요한 두 가지 개념이 있습니다. (그것도 단어입니까?)

이벤트 루프 및 동시성 모델

알아야 할 세 가지가 있습니다. 대기열; 이벤트 루프 와 스택

광범위하고 단순한 용어로, 이벤트 루프는 프로젝트 관리자와 유사하며, 큐와 스택 사이에서 실행하고 통신하려는 모든 기능을 지속적으로 수신합니다.

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

무언가를 실행할 메시지를 받으면 큐에 추가합니다. 대기열은 AJAX 요청과 같이 실행 대기중인 항목의 목록입니다. 다음과 같이 상상하십시오.

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

이러한 메시지 중 하나가 실행될 때 큐에서 메시지를 팝하고 스택을 생성하면 스택은 JS가 메시지에서 명령을 수행하기 위해 실행해야하는 모든 것입니다. 이 예에서는 전화하라는 메시지가 표시됩니다.foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

따라서 foobarFunc가 실행해야하는 모든 것 (이 경우에는 anotherFunction ) 스택으로 푸시됩니다. 이벤트 루프가 대기열의 다음 항목으로 이동하거나 메시지를 듣습니다.

여기서 중요한 것은 실행 순서입니다. 그건

언제 달릴 까

AJAX를 사용하여 외부 당사자에게 전화를 걸거나 비동기 코드 (예 : setTimeout)를 실행하면 Javascript는 응답에 의존하여 진행됩니다.

가장 큰 질문은 언제 응답을 받습니까? 답은 우리가 모른다는 것입니다. 따라서 이벤트 루프는 그 메시지가 "hey run me"라는 메시지를 기다리고 있습니다. JS가 해당 메시지를 동기식으로 기다리면 앱이 멈추고 빨라집니다. 따라서 JS는 메시지가 큐에 다시 추가되기를 기다리는 동안 큐에서 다음 항목을 계속 실행합니다.

이것이 비동기 기능으로 콜백 이라는 것을 사용하는 이유입니다 . 그것은 문자 그대로 약속 같은 것 입니다. 어느 시점에서 무언가를 반환하겠다고 약속 한 것처럼 jQuery는 deffered.done deffered.failand 라고 불리는 특정 콜백을 사용합니다 deffered.always. 여기에서 모두 볼 수 있습니다

따라서 당신이해야 할 일은 전달 된 데이터로 특정 시점에서 실행되도록 약속 된 함수를 전달하는 것입니다.

콜백은 즉시 실행되지 않지만 나중에 실행되지 않은 함수에 대한 참조를 전달하는 것이 중요합니다. 그래서

function foo(bla) {
  console.log(bla)
}

대부분의 경우 (항상 그런 것은 아님) 통과 foo하지 못할 것입니다.foo()

잘만되면 그것이 의미가있을 것입니다. 혼란스러워 보이는 것과 같은 것들이 생길 때-최소한 문서를 완전히 이해하는 것이 좋습니다. 훨씬 더 나은 개발자가 될 것입니다.


18

ES2017을 사용하면 이것을 함수 선언으로해야합니다.

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

그리고 이렇게 실행합니다.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

또는 약속 구문

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})

두 번째 기능을 재사용 할 수 있습니까?
줌 둠미

oncolse, log가 호출되면 결과를 어떻게 사용합니까? 그 시점에서 모든 것이 콘솔로 가지 않습니까?
Ken Ingram
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.