source

비동기 콜에서 응답을 반환하려면 어떻게 해야 하나요?

factcode 2022. 10. 27. 22:59
반응형

비동기 콜에서 응답을 반환하려면 어떻게 해야 하나요?

함수의 응답/결과를 반환하려면 어떻게 해야 합니까?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`
}

→ 다른 예제를 사용한 비동기 동작에 대한 보다 일반적인 설명은 을 참조하십시오. 함수 내부에서 변수를 수정해도 변경되지 않는 이유는 무엇입니까? - 비동기 코드 참조

→ 문제를 이미 이해한 경우, 아래의 가능한 해결책으로 건너뜁니다.

문제

AjaxA비동기(asynchronous)를 나타냅니다.즉, 요청을 보내는 것(또는 응답을 받는 것)이 일반 실행 흐름에서 제외됩니다.이 예에서는$.ajax즉시 반환하고 다음 문장이 반환됩니다.return result;로 전달된 함수보다 먼저 실행됩니다.success콜백도 호출되었습니다.

다음은 동기 흐름과 비동기 흐름의 차이를 명확하게 하는 유추입니다.

동기

친구에게 전화를 걸어 당신을 위해 무언가를 찾아달라고 부탁한다고 상상해 보세요.비록 시간이 좀 걸릴지 모르지만, 당신은 전화를 기다리며 당신의 친구가 당신에게 필요한 답을 줄 때까지 허공을 응시한다.

「normal」코드를 포함한 함수 콜을 발신해도 같은 현상이 발생합니다.

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 실행 시간에는 상한이 있으며 브라우저는 실행을 계속할지 여부를 사용자에게 묻습니다.

이로 인해 사용자 환경이 매우 나빠집니다.사용자는 모든 것이 제대로 작동하는지 알 수 없습니다.게다가 접속이 느린 유저에게는, 그 영향이 더 나빠집니다.

다음에서는 서로 구축되어 있는 3가지 솔루션에 대해 설명합니다.

  • 와의 약속(ES2017+, 트랜스필러 또는 재생기를 사용하는 경우 이전 브라우저에서 사용 가능)
  • 콜백(노드에서 널리 사용됨)
  • 와의 약속(ES2015+, 여러 약속 라이브러리 중 하나를 사용하는 경우 이전 브라우저에서 사용 가능)

이 세 가지 모두 현재 브라우저 및 노드 7+에서 사용할 수 있습니다.


ES2017+: 약속

2017년에 출시된 ECMAScript 버전에서는 비동기 함수에 대한 구문 수준 지원이 도입되었습니다.의 도움으로async그리고.await, 「동기식」으로 비동기식으로 쓸 수 있습니다.코드는 아직 비동기이지만 읽기/이해하기 쉽습니다.

async/await약속을 기반으로 구축:async함수는 항상 약속을 반환합니다. await약속을 "해제"하여 약속이 해결된 값을 가져오거나 약속이 거부되면 오류를 발생시킵니다.

중요:사용할 수 있는 것은await안쪽에서async기능 또는 JavaScript 모듈에 있습니다.톱 레벨await모듈 외부에서는 지원되지 않기 때문에 비동기 IIE(즉시 호출된 함수 표현)를 사용하여asynccontext(모듈을 사용하지 않는 경우).

MDN 및 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 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에 전달되었을 때입니다.Function 2는 Function 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 응답 수신은 이벤트일 뿐입니다.서드파티 코드로 작업해야 할 경우 어려움이 발생할 수 있지만 대부분의 문제는 애플리케이션 흐름에서 생각만 하면 해결할 수 있습니다.


ES2015+: 그 때()와의 약속

Promise API는 ECMAScript 6(ES2015)의 신기능이지만 브라우저 지원은 이미 양호합니다.표준 Promise 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; }

이 약속의 장점을 모두 설명하는 것은 이 답변의 범위를 벗어나지만, 새로운 코드를 작성할 경우에는 진지하게 검토해야 합니다.코드를 매우 추상화하고 분리할 수 있습니다.

약속에 대한 자세한 내용은 HTML5 rocks - JavaScript Promises입니다.

참고: 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 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 Delferred 개체를 즉시 반환합니다.즉,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 없음

개체를 직접 사용하는 경우,false의 세 번째 인수로서

j쿼리

jQuery를 사용하는 경우,async할 수 있는 선택권false이 옵션은 jQuery 1.8 이후 권장되지 않습니다.그 후, 다음의 어느쪽인가를 사용할 수 있습니다.success콜백 또는 액세스responseTextjqXHR 객체의 속성:

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

다음과 같은 다른 jQuery Ajax 메서드를 사용하는 경우$.get,$.getJSON, 등으로 변경해야 합니다.$.ajax(설정 파라미터를 전달할 수 있는 것은$.ajax).

조심해!동기 JSONP 요구는 할 수 없습니다.JSONP는 본질적으로 항상 비동기적입니다(이 옵션을 고려하지 않아도 되는 또 하나의 이유).

코드에서 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를 사용하는 사람들을 위해 훌륭한 답변을 작성했지만, 저는 그렇지 않은 사람들을 위해 대안을 제공하기로 결정했습니다.

(새로운 API, Angular 또는 아래에 다른 답변을 추가했습니다.)


당면한 과제

이것은 다른 답변의 "문제 설명"을 간략하게 요약한 것입니다. 이 글을 읽고 잘 모르겠으면 읽어보세요.

AJAX의 A비동기(asynchronous)를 나타냅니다.즉, 요청을 보내는 것(또는 응답을 받는 것)이 일반 실행 흐름에서 제외됩니다.이 예에서는 가 즉시 반환하고 다음 문이 반환됩니다.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 시작 가이드를 읽어보십시오.

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. 새로운 부하를 사용합니다(ready state & status를 확인할 필요가 없습니다).
  4. XMLHttpRequest 1을 짜증나게 하는 다른 상황도 기억나지 않습니다.

이 Ajax 콜의 응답을 취득하는 방법에는 다음 2가지가 있습니다(XMLHttpRequest var name을 사용하여3가지).

가장 심플한 것:

this.response

아니면 어떤 이유로든 당신이bind()클래스 콜백:

e.target.response

예를 들어:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

또는 (상기의 어나니머스 함수는 항상 문제가 됩니다)

ajax('URL', function(e){console.log(this.response)});

쉬운 일은 없어.

일부에서는 onready state change 또는 XMLHttpRequest 변수명을 사용하는 것이 좋다고 말할 수 있습니다.틀렸어.

XMLHttpRequest 고급 기능을 확인합니다.

모든 *현대 브라우저를 지원했습니다.XMLHttpRequest 2가 작성된 이후 이 방법을 사용하고 있기 때문에 확인할 수 있습니다.어떤 브라우저에서도 문제가 발생한 적이 없습니다.

onready state change는 상태2의 헤더를 취득하는 경우에만 도움이 됩니다.

사용방법XMLHttpRequestvariable name은 onload/oready statechange closure 내에서 콜백을 실행해야 하는 또 하나의 큰 오류입니다.그렇지 않으면 콜백이 손실됩니다.


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 어레이 버퍼로 설정하거나 하는 경우에 도움이 될 수 있습니다.

방법으로서 「POSTAPAP」를 패스해도, 에러는 발생하지 않습니다.

fdggdgilfghfldj를 폼 데이터로 전달해도 오류가 발생하지 않습니다.

첫 번째 경우 에러는 에러입니다.displayAjax()아래this.statusText~하듯이Method not Allowed.

두 번째 케이스에서는, 간단하게 동작합니다.올바른 포스트 데이터를 전달했는지 서버 측에서 확인해야 합니다.

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

에러 응답에는, 에러 코드는 없습니다.

이 밖에 없습니다.this.type에러로 설정되어 있습니다.

에러를 전혀 제어할 수 없는데 에러 핸들러를 추가하는 이유는 무엇입니까?대부분의 오류는 콜백 함수로 반환됩니다.displayAjax().

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

PS: 첫 번째 테스트로 x('x', displayAjax...)라고 썼더니 완전히 반응이...? 그래서 HTML이 있는 폴더를 확인해보니 'x.xml'이라는 파일이 있었습니다. 따라서 파일 확장자를 잊어버린 경우에도 XMLHttpRequest 2 WIL FIND IT가 실행됩니다.나는 LOL'd


동기화된 파일 읽기

그러지마세요.

브라우저를 잠시 차단하려면 큰 파일을 로드하십시오..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');

비동기적인 방법으로 이것을 실행하는 다른 방법은 없습니다.(set Timeout 루프를 사용하면...진지하게요?)

또 다른 포인트는...API나 자신의 목록 파일 또는 요청마다 항상 다른 함수를 사용하는 경우...

항상 같은 XML/JSON 또는 필요한 기능을 로드하는 페이지가 있는 경우에만 해당됩니다.이 경우 Ajax 함수를 약간 수정하고 b를 특수 함수로 바꿉니다.


위의 기능은 기본적인 용도로 사용됩니다.

기능을 확장하려면...

네, 가능합니다.

저는 많은 API를 사용하고 있으며, 모든 HTML 페이지에 통합되는 첫 번째 함수 중 하나는 이 답변의 첫 번째 Ajax 함수이며, GET 전용입니다.

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

다운로드 매니저(레줌, 파일 리더, 파일 시스템 양쪽 범위 사용), 캔버스를 사용하여 다양한 이미지 리사이저를 변환하고 웹 SQL 데이터베이스를 base64 이미지로 채우는 등...

단, 이 경우 그 목적으로만 함수를 생성해야 합니다.때로는 BLOB, 어레이 버퍼가 필요합니다.헤더를 설정하거나 mimtype을 덮어쓸 수 있습니다.그리고 그 밖에도...

하지만 여기서 문제는 어떻게 아약스의 답변을...(간단한 방법을 추가했습니다.)

만약 당신이 약속을 사용한다면, 이 답은 당신을 위한 것입니다.

즉, AngularJS, jQuery(지연), 네이티브 XHR의 대체(페치), 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핸들러는 항상 비동기적으로 실행됩니다.즉, 핸들러에 없는 아래의 코드 다음에 실행됩니다..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

가치dataundefined그 이후data = 5부품이 아직 실행되지 않았습니다.1초 안에 실행될 가능성이 높지만 반환된 값과 무관합니다.

작업이 아직 수행되지 않았으므로(Ajax, 서버 호출, I/O 및 타이머) 요청이 해당 값을 코드에 알리기 전에 값을 반환합니다.

이 문제에 대한 가능한 해결책 중 하나는 계산 완료 시 프로그램을 어떻게 해야 하는지 다시 코드화하는 것입니다.약속은 본질적으로 시간적(시간에 민감함)이므로 이를 능동적으로 가능하게 합니다.

약속에 대한 빠른 재점검

약속은 시간의 경과에 따른 가치입니다.약속은 위엄이 있다.이들은 값 없이 보류 상태로 시작하여 다음과 같이 처리될 수 있습니다.

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

약속은 상태를 한 번만 바꿀 수 있으며 그 후에는 항상 같은 상태를 유지할 수 있습니다.첨부할 수 있습니다.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);
    });
}

set Timeout을 약속을 사용하도록 변환한 후에는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,....반복할 수 있습니다.이것은 그 자체로 흥미롭고 많은 가능성을 열어주는 반면, 한 가지 특별한 사례가 있다.

우리가 생성하는 시퀀스가 숫자가 아닌 일련의 동작일 경우, 우리는 동작이 발생할 때마다 기능을 일시 중지하고 기능을 재개하기 전에 기다릴 수 있습니다.그래서 우리는 일련의 숫자 대신 일련의 미래 가치, 즉 약속들이 필요합니다.

이것은 다소 까다롭지만 매우 강력한 트릭입니다. 동기식으로 비동기 코드를 작성합시다.당신을 위해 이것을 해주는 "달리는 사람"이 몇 명 있습니다.1행은 짧은 코드 몇 줄이지만 이 답변의 범위를 벗어납니다.나는 블루버드를 사용할 것이다.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약속.이는 위의 ES6 제안에서는 '설탕'(nicer 구문)을 추가했을 뿐입니다.async그리고.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
}

그대로 약속을 반환합니다. : )

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

송신 핸들러에 아무것도 반환하지 않습니다.대신 데이터를 전달하거나 성공 함수 내에서 원하는 작업을 직접 수행해야 합니다.

나는 끔찍하게 생긴 손으로 그린 만화로 대답할 것이다.두 번째 이미지 때문에resultundefined를 참조해 주세요.

enter image description here

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

각도 1

AngularJs를 사용하는 사람들은 약속을 통해 이 상황에 대처할 수 있다.

여기 써있네요.

약속은 비동기 함수를 풀 때 사용할 수 있으며 여러 함수를 연결할 수 있습니다.

여기에서도 좋은 설명을 찾을 수 있어요.

아래 설명서에서 볼 수 있는 예가 있습니다.

  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.

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

또한, 여기 약속의 사양이 있습니다.

이 답변의 대부분은 비동기 조작을 1회 실시하는 경우에 도움이 됩니다만, 어레이나 그 외의 리스트와 같은 구조의 각 엔트리에 대해서 비동기 조작을 실시할 필요가 있는 경우에 표시됩니다.그 유혹은 다음과 같습니다.

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., 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); // E.g., 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); // E.g., 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)); // E.g., 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콜이 미처리 상태일 때 변경된다...)

델의 사용 방법에 주목해 주세요.index부터forEach결과를 저장하다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(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약속을 반환하고 다음 작업을 수행합니다.)

한다면doSomethingAsyncPromise는 다음과 같은 것을 사용할 수 있습니다.

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약속하고 아래를 실천할 수 있도록...)

한다면doSomethingAsyncPromise를 제공합니다.ES2017+ 구문을 사용할 수 있는 경우(아마도 Babel과 같은 트랜스필러와 함께), 및 다음과 같은 함수를 사용할 수 있습니다.

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 reduced" 패턴을 변형하여 사용할 수 있습니다(이러한 패턴은 일반적인 Promise reduced보다 더 복잡합니다.이것은 결과를 하나의 결과에서 다른 것으로 전달하는 것이 아니라 그 결과를 배열로 수집하기 때문입니다).

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

다음 예를 참조하십시오.

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.display)가 실행됩니다(통상적인 비동기 흐름).

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

많은 새로운 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
});

자세한 내용은 이러한 비동기 작업을 수행하는 새로운 방법인 약속 및 관찰 자료를 참조하십시오.

이는 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.

그럼 이 문제에 어떻게 대처해야 할까요?

우리의 직무에 약속을 요청합시다.약속이란 그 의미가 무엇인지에 관한 것입니다.즉, 이 함수는 미래에 얻을 수 있는 모든 출력을 사용자에게 제공한다는 것을 의미합니다.이제 위의 작은 문제에 대해 실행해 보겠습니다.

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값(반환하려는 값)을 지정합니다.즉, 비동기 함수로 반환되는 대신 값을 해결합니다.

UPDATE(비동기/대기)

사용하는 것 외에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);
     }
  }

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

다음은 같은 예를 제시하겠습니다.

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

사용하고 있는 것은resultobject는 비동기 조작 중 값을 저장합니다.따라서 비동기 작업 후에도 결과를 사용할 수 있습니다.

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

약속과 콜백은 많은 상황에서 잘 작동하지만 다음과 같은 표현을 하는 것은 골치 아픈 일입니다.

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
}

프로젝트 체크는 이쪽에서 하실 수 있습니다.

내가 쓴 다음 예는 어떻게 하는지 보여준다.

  • 비동기 HTTP 콜 처리
  • 각 API 호출로부터의 응답을 기다립니다.
  • Promise 패턴 사용;
  • Promise를 사용합니다.여러 HTTP 콜에 참여하기 위한 all pattern;

이 작업 예는 자급자족입니다.창을 사용하는 단순한 요청 개체를 정의합니다.XMLHttpRequest콜 발신 거부.수많은 약속이 완료될 때까지 기다리는 간단한 기능을 정의합니다.

맥락.이 예에서는 다음을 검색하기 위해 Spotify Web API 엔드포인트를 쿼리하고 있습니다.playlist특정 쿼리 문자열 세트의 객체:

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

각 항목에 대해 새로운 Promise가 블록을 실행합니다.ExecutionBlock결과를 해석하고 결과 배열(Spotify 목록)에 따라 새로운 약속 세트를 스케줄링합니다.user오브젝트 및 새로운 HTTP 콜을 실행합니다.ExecutionProfileBlock비동기적으로

그런 다음 네스트된 Promise 구조를 볼 수 있습니다.이 구조에서는 여러 개의 완전히 비동기화된 네스트된HTTP 콜을 생성하여 콜의 각 서브셋에서 얻은 결과에 접속할 수 있습니다.Promise.all.

주의: 최근 SpotifysearchAPI는 요청 헤더에 액세스 토큰을 지정해야 합니다.

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

저는 이 솔루션에 대해 여기서 폭넓게 논의해 왔습니다.

간단히 말하면 다음과 같은 콜백을 구현해야 합니다.

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

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

JavaScript는 싱글 스레드입니다.

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

  1. 이벤트 루프

  2. 웹 API

  3. 이벤트 큐

이벤트 루프는 무한 루프처럼 영원히 계속됩니다.이벤트 큐는 이벤트(예: 클릭)에서 모든 기능이 푸시되는 곳입니다.

이것은 큐에서 하나씩 실행되어 이벤트루프에 들어갑니다이러한 루프에서는 이 기능이 실행되고 첫 번째 루프 실행 후 다음 루프에 대비합니다.즉, 큐에 있는 함수가 이벤트 루프에서 실행될 때까지 함수의 실행이 시작되지 않습니다.

이제 큐에 두 개의 함수를 푸시했다고 가정해 보겠습니다.하나는 서버에서 데이터를 가져오는 것이고 다른 하나는 해당 데이터를 이용하는 것입니다.먼저 큐에 serverRequest() 함수를 푸시하고 다음으로 utiliseData() 함수를 푸시했습니다.serverRequest 함수는 이벤트루프에 들어가 서버에서 데이터를 가져오는 데 시간이 얼마나 걸릴지 알 수 없기 때문에 이 프로세스에 시간이 걸릴 것으로 예상되기 때문에 이벤트루프가 비지 상태가 되어 페이지가 행업합니다.

여기서 Web API가 역할을 담당합니다.이벤트 루프에서 이 기능을 가져와 큐에서 다음 기능을 실행할 수 있도록 이벤트 루프를 프리하게 하는 서버를 처리합니다.

큐의 다음 함수는 utiliseData()로 루프를 통과하지만 데이터가 없기 때문에 낭비되고 큐의 마지막까지 다음 함수의 실행이 계속됩니다.(이것은 비동기 호출이라고 불립니다.즉, 데이터를 얻을 때까지 다른 작업을 할 수 있습니다.)

serverRequest() 함수에 코드로 반환문이 있다고 가정합니다.서버 Web API에서 데이터를 받으면 데이터를 큐 끝에 있는 큐에 푸시합니다.

큐의 마지막에 푸시되기 때문에 큐에 이 데이터를 사용하는 기능이 남아 있지 않기 때문에 해당 데이터를 사용할 수 없습니다.따라서 비동기 콜에서 무언가를 반환할 수 없습니다.

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

서버 호출 기능에 기능(서버로부터 반환된 데이터를 이용한 기능)을 부여합니다.

Callback

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 콜백

2017년 답변: 현재 모든 브라우저와 Node.js에서 원하는 작업을 수행할 수 있습니다.

이것은 매우 간단합니다.

  • 약속의 반환
  • "ait"를 사용하면 JavaScript에 HTTP 응답과 같은 값으로 해결되는 약속을 대기하도록 지시합니다.
  • 부모 함수에 'async' 키워드를 추가합니다.

다음은 사용 중인 코드 버전입니다.

(async function(){

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

})()

wait는 현재 모든 브라우저 및 Node.js 8에서 지원됩니다.

이 커스텀 라이브러리(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)
});

또 다른 해결책은 순차 실행 프로그램 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-aware 래퍼에 랩합니다(프로미스 버전이 있는 경우 이 스텝은 생략할 수 있습니다).

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단계. nsynj를 통해 동기 방식으로 기능을 실행합니다.

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs는 모든 연산자와 식을 단계별로 평가하며, 일부 느린 함수의 결과가 준비되지 않은 경우 실행을 일시 중지합니다.

여기 더 많은 예가 있습니다.

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 또는 closure-compiler를 통해 코드를 실행하여 ECMAScript 5를 생성할 수 있습니다.

콜백...args는, 복수의 인수가 있는 콜백을 패턴으로 처리할 수 있도록, 판독시에 어레이로 랩 되어 디스테그레이드 됩니다.를 들어 노드 fs:

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

우리는 우리가 "시간"이라고 부르는 차원을 따라 진행되는 것처럼 보이는 우주에 있는 자신을 발견한다.우리는 시간이 무엇인지 잘 모르지만, "과거", "현재", "미래", "전", "후"와 같이 추론하고 이야기할 수 있는 추상적 개념과 어휘를 개발했습니다.

우리가 만드는 컴퓨터 시스템은 점점 더 많은 시간을 중요한 차원으로 가지고 있습니다.장래에 일어날 일이 정해져 있다.그리고 그 첫 번째 일이 일어난 후에 다른 일들이 일어나야 한다.이것은 "비동기성"이라고 불리는 기본적인 개념이다.네트워크가 확대되고 있는 오늘날, 비동기성의 가장 일반적인 경우는, 리모트 시스템이 요구에 응답하는 것을 기다리는 것입니다.

예를 들어보자.우유 배달원에게 전화해서 우유를 주문하세요.커피가 오면 커피에 넣고 싶을 거예요.커피가 아직 안 와서 지금 우유를 넣으면 안 돼요.커피에 넣기 전에 올 때까지 기다려야 해요.즉, 다음 기능이 작동하지 않습니다.

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

왜냐하면 JavaScript는 대기할 필요가 있다는 것을 알 방법이 없기 때문입니다.order_milk실행 전에 끝내다put_in_coffee다시 말해, 그것은 그것을 알지 못한다.order_milk비동기적이기 때문에 미래까지 우유가 나오지 않을 것입니다.JavaScript 및 기타 선언형 언어는 기다리지 않고 차례로 문을 실행합니다.

이 문제에 대한 전형적인 자바스크립트 접근법은 JavaScript가 전달 가능한 퍼스트 클래스 오브젝트로서의 기능을 지원한다는 사실을 이용하여 비동기 요구에 함수를 전달하고 나중에 작업을 완료하면 호출합니다.이것이 '콜백' 접근법입니다.다음과 같습니다.

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_coffee로서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); });

When using promises, we end up passing lots of functions into then, so it's often helpful to use the more compact ES6-style arrow functions:

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

The async keyword

But there's still something vaguely dissatisfying about having to write code one way if synchronous and a quite different way if asynchronous. For synchronous, we write

a();
b();

but if a is asynchronous, with promises we have to write

a() . then(b);

Above, we said, "JavaScript has no way to know that it needs to wait for the first call to finish before it executes the second". Wouldn't it be nice if there was some way to tell JavaScript that? It turns out that there is--the await keyword, used inside a special type of function called an "async" function. This feature is part of the upcoming version of ECMAScript (ES), but it is already available in transpilers such as Babel given the right presets. This allows us to simply write

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

In your case, you would be able to write something like

async function foo() {
  data = await get_data();
  console.log(data);
}

Short answer: Your foo() method returns immediately, while the $ajax() call executes asynchronously after the function returns. The problem is then how or where to store the results retrieved by the async call once it returns.

Several solutions have been given in this thread. Perhaps the easiest way is to pass an object to the foo() method, and to store the results in a member of that object after the async call completes.

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

Note that the call to foo() will still return nothing useful. However, the result of the async call will now be stored in result.response.

Here are some approaches to work with asynchronous requests:

  1. Browser Promise object
  2. Q - A promise library for JavaScript
  3. A+ Promises.js
  4. jQuery deferred
  5. XMLHttpRequest API
  6. Using callback concept - As implementation in first answer

Example: jQuery deferred implementation to work with multiple requests

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

Use a callback() function inside the foo() success. Try it in this way. It is simple and easy to understand.

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

1. A first stumbling step

As for many others, my encounter with asynchronous calls was puzzling at first.
I don't remember the details, but I may have tried something like:

let result;

$.ajax({
  url: 'https://jsonplaceholder.typicode.com/todos/1',
  success: function (response) {
    console.log('\nInside $.ajax:');
    console.log(response);
    result = response;
  }
});

console.log('Finally, the result: ' + result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

Whoops! The output of the line console.log('Finally, the result: ' + result); which I thought would be printed last, is actually printed before the other output! – And it doesn't contain the result: it just prints undefined. 1 How come?

A helpful insight

I distinctly remember my first aha! moment on how to understand asynchronous calls.
It was this comment saying:
you actually don't want to get the data out of a callback;
you want to get your data-needing action into the callback!
2

This is obvious in the example above.
But is it still possible to write code after the asynchronous call that deals with the response once it has completed?

2. Plain JavaScript and a callback function

The answer is yes! – It is possible.
One alternative is the use of a callback function in a continuation-passing style: 3

const url = 'https://jsonplaceholder.typicode.com/todos/2';

function asynchronousCall (callback) {
  const request = new XMLHttpRequest();
  request.open('GET', url);
  request.send();
  request.onload = function () {
    if (request.readyState === request.DONE) {
      console.log('The request is done. Now calling back.');
      callback(request.responseText);
    }
  };
}

asynchronousCall(function (result) {
  console.log('This is the start of the callback function. Result:');
  console.log(result);
  console.log('The callback function finishes on this line. THE END!');
});

console.log('LAST in the code, but executed FIRST!');
.as-console-wrapper { max-height: 100% !important; top: 0; }

Note how the function asynchronousCall is void. It returns nothing. Instead, by calling asynchronousCall with an anonymous callback function (asynchronousCall(function (result) {...), this function executes the desired actions on the result, but only after the request has completed – when the responseText is available.

Running the above snippet shows how I will probably not want to write any code after the asyncronous call (such as the line LAST in the code, but executed FIRST!).
Why? – Because such code will happen before the asyncronous call delivers any response data.
Doing so is bound to cause confusion when comparing the code with the output.

3. Promise with .then() – or async/await

The .then() construct was introduced in the ECMA-262 6th Edition in June 2015, and the async/await construct was introduced in the ECMA-262 8th Edition in June 2017.
The code below is still plain JavaScript, replacing the old-school XMLHttpRequest with Fetch. 4

fetch('http://api.icndb.com/jokes/random')
  .then(response => response.json())
  .then(responseBody => {
    console.log('.then() - the response body:');
    console.log(JSON.stringify(responseBody) + '\n\n');
  });

async function receiveAndAwaitPromise () {
  const responseBody =
    (await fetch('http://api.icndb.com/jokes/random')).json();
  console.log('async/await:');
  console.log(JSON.stringify(await responseBody) + '\n\n');
}

receiveAndAwaitPromise();
.as-console-wrapper { max-height: 100% !important; top: 0; }

A word of warning is warranted if you decide to go with the async/await construct. Note in the above snippet how await is needed in two places. If forgotten in the first place, there will be no output. If forgotten in the second place, the only output will be the empty object, {} (or [object Object] or [object Promise]).
Forgetting the async prefix of the function is maybe the worst of all – the output will be "SyntaxError: missing ) in parenthetical" – no mentioning of the missing async keyword.

4. Promise.all – array of URLs 5

Suppose we need to request a whole bunch of URLs. I could send one request, wait till it responds, then send the next request, wait till it responds, and so on ...
Aargh! – That could take a loong time. Wouldn't it be better if I could send them all at once, and then wait no longer than it takes for the slowest response to arrive?

As a simplified example, I will use:

urls = ['https://jsonplaceholder.typicode.com/todos/2',
        'https://jsonplaceholder.typicode.com/todos/3']

The JSONs of the two URLs:

{"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
 "completed":false}
{"userId":1,"id":3,"title":"fugiat veniam minus","completed":false}

The goal is to get an array of objects, where each object contains the title value from the corresponding URL.

To make it a little more interesting, I will assume that there is already an array of names that I want the array of URL results (the titles) to be merged with:

namesonly = ['two', 'three']

The desired output is a mashup combining namesonly and urls into an array of objects:

[{"name":"two","loremipsum":"quis ut nam facilis et officia qui"},
{"name":"three","loremipsum":"fugiat veniam minus"}]

where I have changed the name of title to loremipsum.

const namesonly = ['two','three'];

const urls = ['https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'];

Promise.all(urls.map(url => fetch(url)
  .then(response => response.json())
  .then(responseBody => responseBody.title)))
  .then(titles => {
    const names = namesonly.map(value => ({ name: value }));
    console.log('names: ' + JSON.stringify(names));
    const latins = titles.map(value => ({ loremipsum: value }));
    console.log('latins:\n' + JSON.stringify(latins));
    const result =
      names.map((item, i) => Object.assign({}, item, latins[i]));
    console.log('result:\n' + JSON.stringify(result));
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

All the above examples are short and succinctly convey how asynchronous calls may be used on toyish APIs. Using small APIs works well to explain concepts and working code, but the examples might be a bit of dry runs.

The next section will show a more realistic example on how APIs may be combined to create a more interesting output.

5. How to visualize a mashup in Postman 6

The MusicBrainz API has information about artists and music bands.
An example – a request for the British rock band Coldplay is:
http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?&fmt=json&inc=url-rels+release-groups.
The JSON response contains – among other things – the 25 earliest album titles by the band. This information is in the release-groups array. The start of this array, including its first object is:

...
  "release-groups": [
    {
      "id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
      "secondary-type-ids": [],
      "first-release-date": "2000-07-10",
      "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
      "disambiguation": "",
      "secondary-types": [],
      "title": "Parachutes",
      "primary-type": "Album"
    },
...

This JSON snippet shows that the first album by Coldplay is Parachutes. It also gives an id, in this case 1dc4c347-a1db-32aa-b14f-bc9cc507b843, which is a unique identifier of the album.

This identifier can be used to make a lookup in the Cover Art Archive API:
http://coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b843. 7

For each album, the JSON response contains some images, one of which is the front cover of the album. The first few lines of the response to the above request:

{
  "images": [
    {
      "approved": true,
      "back": false,
      "comment": "",
      "edit": 22132705,
      "front": true,
      "id": 4086974851,
      "image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
      "thumbnails": {
        "250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
        "500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
        "1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
        "large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
    },
...

Of interest here is the line "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg".
That URL is a direct link to the front cover of the Parachutes album.

The code to create and visualize the mashup

The overall task is to use Postman to visualize all the album titles and front covers of a music band. How to write code to achieve this has already been described in quite some detail in an answer to the question How can I visualize an API mashup in Postman? – Therefore I will avoid lengthy discussions here and just present the code and a screenshot of the result:

const lock = setTimeout(() => {}, 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
  pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => {
  const bandName = responseO.json().name;
  const albums = responseO.json()['release-groups'];
  for (const item of albums) {
    albumsArray.push(item.title);
    urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
  }
  albumsArray.length = urlsArray.length = 15;
  const images = [];
  let countDown = urlsArray.length;
  urlsArray.forEach((url, index) => {
    asynchronousCall(url, imageURL => {
      images[index] = imageURL;
      if (--countDown === 0) { // Callback for ALL starts on next line.
        clearTimeout(lock); // Unlock the timeout.
        const albumTitles = albumsArray.map(value => ({ title: value }));
        const albumImages = images.map(value => ({ image: value }));
        const albumsAndImages = albumTitles.map(
          (item, i) => Object.assign({}, item, albumImages[i]));
        const template = `<table>
          <tr><th>` + bandName + `</th></tr>
          {{#each responseI}}
          <tr><td>{{title}}<br><img src="{{image}}"></td></tr>
          {{/each}}
        </table>`;
        pm.visualizer.set(template, { responseI: albumsAndImages });
      }
    });
  });
  function asynchronousCall (url, callback) {
    pm.sendRequest(url, (_, responseI) => {
      callback(responseI.json().images.find(obj => obj.front === true)
        .thumbnails.small); // Individual callback.
    });
  }
});


The result and documentation

Result and documentation in Postman


How to download and run the Postman Collection

Running the Postman Collection should be straightforward.
Assuming you are using the desktop version of Postman, do as follows:

  1. Download and save
    http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.json
    in a suitable place on your hard drive.

  2. In Postman, Ctrl + O > Upload Files > MusicBands.pm_coll.json > Import.
    You should now see MusicBands among your collections in Postman.

  3. Collections > MusicBands > DummyRequest > Send. 8

  4. In the Postman Response Body, click Visualize.

  5. You should now be able to scroll 15 albums as indicated by the screenshot above.

References


1 Expressed by the original poster as: they all return undefined.
2 If you think asynchronous calls are confusing, consider having a look at some questions and answers about asynchronous calls to see if that helps.
3 The name XMLHttpRequest is as misleading as the X in AJAX – these days the data format of Web APIs is ubiquitously JSON, not XML.
4Fetch returns a Promise. I was surprised to learn that neither XMLHttpRequest nor Fetch are part of the ECMAScript standard. The reason JavaScript can access them here is because the web browser provides them. The Fetch Standard and the XMLHttpRequest Standard are both upheld by the Web Hypertext Application Technology Working Group (WHATWG) that was formed in June 2004.
5 This section borrows a lot from How can I fetch an array of URLs with Promise.all?.
6 This section relies heavily on How can I visualize an API mashup in Postman?.
7 This URL is automatically redirected to: https://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json.
8 If you get an error, Something went wrong while running your scripts, try hitting Send again.

Using Promise

The most perfect answer to this question is using 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);
  });
}

Usage

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

But wait...!

There is a problem with using promises!

Why should we use our own custom Promise?

I was using this solution for a while until I figured out there is an error in old browsers:

Uncaught ReferenceError: Promise is not defined

So I decided to implement my own Promise class for ES3 to below JavaScript compilers if it's not defined. Just add this code before your main code and then safely use 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;
    }();
}

Of course there are many approaches like synchronous request, promise, but from my experience I think you should use the callback approach. It's natural to asynchronous behavior of JavaScript.

So, your code snippet can be rewritten to be a little different:

function foo() {
    var result;

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

    return result;
}

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

The question was:

How do I return the response from an asynchronous call?

which can be interpreted as:

How to make asynchronous code look synchronous?

The solution will be to avoid callbacks, and use a combination of Promises and async/await.

I would like to give an example for an Ajax request.

(Although it can be written in JavaScript, I prefer to write it in Python, and compile it to JavaScript using Transcrypt. It will be clear enough.)

Let’s first enable jQuery usage, to have $ available as S:

__pragma__ ('alias', 'S', '$')

Define a function which returns a Promise, in this case an Ajax call:

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

Use the asynchronous code as if it were synchronous:

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")

Rather than throwing code at you, there are two concepts that are key to understanding how JavaScript handles callbacks and asynchronicity (is that even a word?)

The Event Loop and Concurrency Model

There are three things you need to be aware of; The queue; the event loop and the stack

In broad, simplistic terms, the event loop is like the project manager, it is constantly listening for any functions that want to run and communicates between the queue and the stack.

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

Once it receives a message to run something it adds it to the queue. The queue is the list of things that are waiting to execute (like your AJAX request). imagine it like this:

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

When one of these messages is going to execute it pops the message from the queue and creates a stack, the stack is everything JavaScript needs to execute to perform the instruction in the message. So in our example it's being told to call foobarFunc

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

So anything that foobarFunc needs to execute (in our case anotherFunction) will get pushed onto the stack. executed, and then forgotten about - the event loop will then move onto the next thing in the queue (or listen for messages)

The key thing here is the order of execution. That is

WHEN is something going to run

When you make a call using AJAX to an external party or run any asynchronous code (a setTimeout for example), JavaScript is dependant upon a response before it can proceed.

The big question is when will it get the response? The answer is we don't know - so the event loop is waiting for that message to say "hey run me". If JavaScript just waited around for that message synchronously your app would freeze and it will suck. So JavaScript carries on executing the next item in the queue whilst waiting for the message to get added back to the queue.

That's why with asynchronous functionality we use things called callbacks. - A function or handler that, when passed into another function, will be executed at a later date. A promise uses callbacks (functions passed to .then() for example) as a way to reason about this asynchronous behaviour in a more linear way. The promise is a way of saying "I promise to return something at some point" and the callback is how we handle that value that is eventually returned. jQuery uses specific callbacks called deffered.done deffered.fail and deffered.always (amongst others). You can see them all here

So what you need to do is pass a function that is promised to execute at some point with data that is passed to it.

Because a callback is not executed immediately but at a later time it's important to pass the reference to the function not it executed. so

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

so most of the time (but not always) you'll pass foo not foo()

Hopefully that will make some sense. When you encounter things like this that seem confusing - i highly recommend reading the documentation fully to at least get an understanding of it. It will make you a much better developer.

ReferenceURL : https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call

반응형