비동기 자바 스크립트 함수를 동 기적으로 호출


221

첫째, 이것은 비동기식 호출을 수천 줄의 긴 동기식 코드베이스에 개량하기 위해 의도적으로 잘못된 방식으로 수행하는 매우 구체적인 경우입니다. 맞습니다. " 그것은 내 존재의 모든 섬유를 다치게하지만 현실과 이상은 종종 맞물리지 않습니다. 나는 이것이 짜증나는 것을 안다.

좋아, 그 길에서, 내가 할 수 있도록 어떻게해야합니까?

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

예제 (또는 그 부족)는 모두 라이브러리 및 / 또는 컴파일러를 사용하며 둘 다이 솔루션에는 적합하지 않습니다. UI를 멈추지 않고 블록을 만드는 방법에 대한 구체적인 예가 필요합니다 (예 : 콜백이 호출 될 때까지 doSomething 함수를 떠나지 마십시오). JS에서 그러한 일이 가능하다면.


16
브라우저 블록을 만들고 기다릴 수는 없습니다. 그들은 그렇게하지 않을 것입니다.
Pointy

2
대부분의 브라우저에서 블로킹 메커니즘을 사용하는 javascript dosent ... 비동기 호출이 데이터를 반환하기 위해 호출 될 때 호출되는 콜백을 생성하려고합니다
Nadir Muzaffar

8
브라우저에 "이전 기능을 비동기식으로 실행하라고 말했지만 실제로는 의미가 없었습니다!"라는 방법을 브라우저에 요청하는 것입니다. 왜 그렇게 될 것으로 기대 하십니까?
Wayne

2
편집 해 주셔서 감사합니다. 나는 엄격하게 무례하지는 않았지만 당신의 말이 더 좋습니다.
Robert C. Barth

2
@ RobertC.Barth 이제 JavaScript에서도 가능합니다. 비동기 대기 기능은 아직 표준에서 비준되지 않았지만 ES2017에 계획되어 있습니다. 자세한 내용은 아래 답변을 참조하십시오.
John

답변:


135

"내가"올바른 방법 "또는 무엇을해야하는지에 대해 말하지 마라"

확인. 하지만 당신은 정말로 올바른 방법으로해야합니다.

"UI를 멈추지 않고 차단하는 방법에 대한 구체적인 예가 필요합니다. JS에서 이런 일이 가능하다면."

아니요, UI를 차단하지 않고 실행중인 JavaScript를 차단할 수 없습니다.

정보가 부족한 경우 솔루션을 제공하기가 어렵지만 한 가지 옵션은 호출 함수가 전역 변수를 확인하기 위해 폴링을 수행 한 다음 콜백 data을 전역으로 설정 하는 것입니다.

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);

이 모든 것은 당신이 수정할 수 있다고 가정합니다 doSomething(). 그게 카드에 있는지 모르겠습니다.

그것이 수정 될 수 있다면 doSomething()다른 콜백에서 콜백을 받기 위해 콜백을 전달하는 이유를 모르겠지만 문제가 발생하기 전에 멈추는 것이 좋습니다. ;)


오, 도대체. 올바르게 수행 할 수 있다고 제안하는 예를 제시 했으므로 해당 솔루션을 보여 드리겠습니다 ...

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

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

예제에는 비동기 호출로 전달되는 콜백이 포함되어 있으므로 콜백에서 호출 할 함수를 전달하는 것이 올바른 방법입니다 doSomething().

물론 이것이 콜백이 수행하는 유일한 일이라면 func직접 전달할 것입니다 ...

myAsynchronousCall(param1, func);

22
예, 올바르게하는 방법을 알고 있습니다. 지정된 특정 이유로 잘못 수행 할 수있는 방법 / 방법을 알아야합니다. 요점은 myAsynchronousCall이 콜백 함수에 대한 호출을 완료 할 때까지 doSomething ()을 남기고 싶지 않다는 것입니다. Bleh, 내가 의심 한 것처럼, 나는 그것을 백업하기 위해 인터넷의 수집 된 지혜가 필요했습니다. 감사합니다. :-)
Robert C. Barth

2
@ RobertC.Barth : 네, 의심은 맞습니다.

나 또는 "정확하게 수행 된"버전 만 작동합니까? 이 질문에는 리턴 호출이 포함되어 있으며, 그 전에 비동기 호출이 끝나기를 기다리는 무언가가 있어야합니다.이 답변의 첫 번째 부분은 다루지 않습니다.
ravemir

@ravemir : 대답 그가 원하는 것을 할 수 없다는 것 입니다 . 이해해야 할 중요한 부분입니다. 즉, UI를 차단하지 않고 비동기식 호출을 수행하고 값을 반환 할 수 없습니다. 따라서 첫 번째 해결책은 전역 변수를 사용하고 해당 변수가 수정되었는지 확인하기 위해 폴링하는 못생긴 해킹입니다. 두 번째 버전은 올바른 방법입니다.

1
@ 레오나르도 : 그것은 질문에서 불려지는 신비한 기능입니다. 기본적으로 코드를 비동기 적으로 실행하여 수신해야하는 결과를 생성하는 모든 것을 나타냅니다. 따라서 AJAX 요청과 같을 수 있습니다. callback함수에 함수를 전달하면 myAsynchronousCall비동기 작업을 수행하고 완료되면 콜백을 호출합니다. 여기 데모가 있습니다.

60

ES2017 의 기능인 비동기 함수 는 약속 (특정 형식의 비동기 코드)과 await키워드 를 사용하여 비동기 코드를 동기화 된 모양으로 만듭니다 . 아래 코드 예제 에서 async / await 함수를 나타내는 키워드 async앞에있는 function키워드를 확인하십시오. await키워드는 사전을 고정하는 기능에없이 작동하지 않습니다 async키워드. 현재로서는 예외가 없으므로 최상위 레벨 대기가 작동하지 않습니다 (최상위 레벨은 함수 외부에서 대기를 의미합니다). 최상위에await 대한 제안 이 있지만 .

ES2017은 2017 년 6 월 27 일 JavaScript의 표준으로 비준 (즉 최종)되었습니다. Async await는 이미 브라우저에서 작동 할 수 있지만, 그렇지 않은 경우 babel 또는 traceur 와 같은 자바 스크립트 트랜스 파일러를 사용하여 기능을 계속 사용할 수 있습니다 . Chrome 55는 비동기 기능을 완벽하게 지원합니다. 따라서 최신 브라우저를 사용하는 경우 아래 코드를 사용해 볼 수 있습니다.

브라우저 호환성에 대해서는 kangax의 es2017 호환성 표 를 참조하십시오 .

다음은 async await 함수의 예입니다.이 함수 doAsync는 3 초의 일시 정지를 시작하고 각 일시 정지 후 시작 시간과의 시차를 인쇄합니다.

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();

await 키워드가 약속 값 앞에 놓이면 (이 경우 약속 값은 doSomethingAsync 함수에 의해 리턴 된 값임) await 키워드는 함수 호출의 실행을 일시 정지하지만 다른 함수는 일시 정지하지 않고 계속됩니다. 약속이 해결 될 때까지 다른 코드를 실행합니다. 약속이 해결 된 후에는 약속의 가치가 풀리며 대기 및 약속 표현이 현재 래핑되지 않은 값으로 대체되는 것으로 생각할 수 있습니다.

따라서 await는 대기를 일시 중지 한 다음 나머지 줄을 실행하기 전에 값을 줄 바꿈 해제하기 때문에 배열에서 기다리는 시간 차이를 수집하고 배열을 인쇄하는 아래 예제와 같이 for 루프 및 내부 함수 호출에서 사용할 수 있습니다.

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})

async 함수 자체는 약속을 반환하므로 위와 같이 또는 다른 async await 함수 내에서 체인을 사용하여 약속으로 사용할 수 있습니다.

위의 함수는 요청을 동시에 보내려면 다른 요청을 보내기 전에 각 응답을 기다릴 것 입니다. Promise.all 을 사용할 수 있습니다 .

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})

약속이 거부되면 시도 catch로 랩핑하거나 시도 catch를 건너 뛰고 오류가 async / await 함수 catch 호출로 전파되도록 할 수 있습니다. 특히 Node.js에서 약속 오류를 처리하지 않은 상태로 두지 않도록주의해야합니다. 다음은 오류의 작동 방식을 보여주는 몇 가지 예입니다.

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)

여기 로 가면 다가오는 ECMAScript 버전에 대한 완성 된 제안을 볼 수 있습니다.

ES2015 (ES6)에서만 사용할 수있는 대안은 발전기 기능을 래핑하는 특수 기능을 사용하는 것입니다. 생성기 함수에는 await 키워드를 주변 함수와 복제하는 데 사용할 수있는 yield 키워드가 있습니다. yield 키워드와 생성기 함수는 훨씬 더 일반적인 목적이며 async await 함수가 수행하는 것보다 더 많은 작업을 수행 할 수 있습니다. async await를 복제하는 데 사용할 수있는 생성기 함수 래퍼를 원한다면 co.js를 확인하십시오 . 그런데 비동기 대기 함수와 같은 co의 함수는 약속을 반환합니다. 솔직히이 시점에서 브라우저 호환성은 생성기 함수와 비동기 함수 모두에 대해 거의 동일하므로 비동기 대기 기능을 원한다면 co.js없이 비동기 함수를 사용해야합니다.

브라우저 지원은 실제로 IE를 제외한 모든 주요 현재 브라우저 (Chrome, Safari 및 Edge)의 비동기 기능 (2017 년 현재)에 매우 적합합니다.


2
나는이 답변을 좋아합니다
ycomp

1
우리가 얼마나 멀리 왔는지 :)
Derek

3
이것은 훌륭한 대답이지만 원래 포스터 문제의 경우 문제가 한 수준 위로 올라가는 것만이라고 생각합니다. 그가 doSomething을 await 내부의 비동기 함수로 바꾼다고 가정 해보십시오. 이 함수는 이제 promise를 반환하고 비동기식이므로 해당 함수를 호출 할 때마다 동일한 문제를 다시 처리해야합니다.
dpwrussell 8:20에

1
@dpwrussell 이것은 사실입니다. 코드베이스에 비동기 함수와 약속이 있습니다. 모든 것에 대한 약속을 해결하는 가장 좋은 방법은 동기식 콜백을 작성하는 것입니다.이 twitter.com/sebmarkbage/status/941214259505119232 와 같이 매우 이상하고 논쟁의 여지가없는 한 비동기 값을 동기식으로 반환하는 방법 은 없습니다. 권하다. 질문 끝에 제목에 대한 답변이 아니라 질문에 대한 답변을보다 완전하게 작성하기 위해 질문 끝에 편집을 추가합니다.
John

그것은 큰 대답 +1이며 모두 그대로 작성되었지만, 콜백을 사용하는 것보다 덜 복잡하지 않습니다.
Altimus Prime

47

JQuery Promises를 살펴보십시오.

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

코드를 리팩터링하십시오.

    var dfd = new jQuery.Deferred ();


    함수 callBack (data) {
       dfd.notify (데이터);
    }

    // 비동기 호출을 수행합니다.
    myAsynchronousCall (param1, 콜백);

    함수 doSomething (data) {
     // 데이터로 작업 ...
    }

    $ .when (dfd) .then (doSomething);



3
이 답변에 +1이 맞습니다. 그러나, 나는있는 라인 업데이트 할 dfd.notify(data)dfd.resolve(data)
제이슨

7
이것은 실제로 비동기 적이 지 않고 동기 적이라는 환상을주는 코드의 경우입니까?
saurshaz

2
약속은 잘 구성된 콜백입니다. :) 비동기 호출이 필요한 경우 일부 객체 초기화를 가정 해 봅시다. 약속은 약간의 차이를 만듭니다.
webduvet

10
약속은 동기화되지 않습니다.
Vans S

6

http://taskjs.org/ 에는 멋진 해결 방법이 있습니다.

자바 스크립트에 새로운 생성기를 사용합니다. 따라서 현재 대부분의 브라우저에서 구현되지 않습니다. 파이어 폭스에서 테스트했으며 비동기 기능을 래핑하는 좋은 방법입니다.

다음은 프로젝트 GitHub의 예제 코드입니다

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}

3

당신은 할 수 와 동기로 NodeJS에서 비동기 자바 스크립트를 강제로 동기화-RPC .

그것은 확실히 UI를 고정시킬 것이므로, 필요한 단축키를 사용할 수 있는지 여부에 관해서는 여전히 선구자입니다. NodeJS에서 가끔 차단할 수 있어도 JavaScript에서 One And Only Thread를 일시 중단 할 수 없습니다. 약속이 해결 될 때까지 콜백, 이벤트 및 비동기 항목을 처리 할 수 ​​없습니다. 따라서 독자가 OP와 같은 피할 수없는 상황이 아니거나 콜백, 이벤트 등이없는 영광스러운 쉘 스크립트를 작성하지 않는 한이를 수행하지 마십시오!

그러나이 작업을 수행하는 방법은 다음과 같습니다.

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

제한 사항 :

이 두 가지 모두 sync-rpc구현 방법의 결과입니다 require('child_process').spawnSync.

  1. 브라우저에서는 작동하지 않습니다.
  2. 함수에 대한 인수 직렬화 가능 해야 합니다. 인수는에서 전달되거나 전달 JSON.stringify되지 않으므로 프로토 타입 체인과 같은 함수 및 열거 할 수없는 특성이 손실됩니다.

1

콜백으로 변환 할 수도 있습니다.

function thirdPartyFoo(callback) {    
  callback("Hello World");    
}

function foo() {    
  var fooVariable;

  thirdPartyFoo(function(data) {
    fooVariable = data;
  });

  return fooVariable;
}

var temp = foo();  
console.log(temp);

0

당신이 원하는 것은 실제로 가능합니다. 서비스 워커에서 비동기 코드를 실행하고 웹 워커에서 동기 코드를 실행할 수 있으면 웹 워커가 서비스 워커에게 동기 XHR을 보내도록하고 서비스 워커가 비동기 작업을 수행하는 동안 웹 워커의 스레드가 기다립니다. 이것은 훌륭한 접근 방법은 아니지만 효과가 있습니다.


-4

요구 사항을 약간 조정하면 달성하려는 아이디어를 실현할 수 있습니다.

런타임이 ES6 사양을 지원하는 경우 아래 코드가 가능합니다.

비동기 함수 에 대한 추가 정보

async function myAsynchronousCall(param1) {
    // logic for myAsynchronous call
    return d;
}

function doSomething() {

  var data = await myAsynchronousCall(param1); //'blocks' here until the async call is finished
  return data;
}

4
Firefox에서 오류가 발생했습니다 : SyntaxError: await is only valid in async functions and async generators. param1은 정의되지 않았으며 사용되지도 않습니다.
Harvey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.