일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 소프트웨어공학
- 인공지능
- 보안
- 소프트웨어
- 자료구조
- 알고리즘
- 사이버보안
- 데이터베이스
- 네트워크보안
- 컴퓨터과학
- 프로그래밍언어
- 웹개발
- 파이썬
- 데이터구조
- 클라우드컴퓨팅
- I'm Sorry
- 컴퓨터공학
- 코딩
- 프로그래밍
- 컴퓨터비전
- 딥러닝
- 네트워크
- 버전관리
- 데이터분석
- Yes
- 빅데이터
- 데이터과학
- 자바스크립트
- 머신러닝
- 2
- Today
- Total
스택큐힙리스트
비동기 호출의 응답을 어떻게 반환하나요? 본문
비동기 요청을 하는 함수 foo에서 응답/결과를 어떻게 반환할까요?
내가 콜백에서 값을 반환하려고 하고, 함수 내부에서 결과를 로컬 변수에 할당하고 그 변수를 반환하려고 하지만, 그 어떤 방법도 실제로 응답을 반환하지 않는다 — 모두 변수의 초기값인 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`
}
노드.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`
}
답변 1
→ 다양한 예제와 함께 비동기 동작에 대해 보다 일반적인 설명을 보려면 Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference을(를) 참조하십시오.
→ 만약 이미 문제를 이해하고 있다면, 아래 가능한 해결 방법으로 넘어가세요.
문제
$#*^*$&의 A는 #$%^@&$!$&을 의미합니다. 이것은 요청을 보내는 것 (혹은 응답을 받는 것)이 일반적인 실행 흐름에서 빠져 나온다는 것을 의미합니다. 당신의 예시에서, $#@!#*@$&는 즉시 반환되고, 넘겨준 $#@*!&*!$& 콜백 함수가 호출되기 전에 $#@!*^$!$& 다음 명령문이 실행됩니다.
여기 동기 및 비동기 흐름의 차이를 더 분명하게 설명해주는 비유가 있습니다.
동기의
친구에게 전화를 걸어서 뭔가 찾아보라고 부탁하는 상상을 해보세요. 오래 걸릴지도 모르겠지만 전화기 끝에서 답을 얻을 때까지 빈 공간을 바라보며 기다립니다.
일반 코드를 포함하는 함수 호출을 만들 때 동일한 일이 발생합니다:
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 호출 이후의 문이 실행됩니다. 최종적으로 응답을 받으려면, 응답을 받은 후에 호출될 함수인 콜백(callback) 함수를 제공합니다(call back을 눈치채셨나요?). 그 호출 이후에 오는 어떤 문이든 콜백이 호출되기 전에 실행됩니다.
해결책(들)
자바스크립트의 비동기적 특성을 받아들이세요! 일부 비동기 작업은 동기적인 상대 작업도 제공하지만(Ajax도 마찬가지입니다), 브라우저 환경에서는 일반적으로 그들을 사용하는 것이 권장되지 않습니다.
왜 나쁘다고 생각하시나요?
자바스크립트는 브라우저의 UI 스레드에서 실행되며, 장기 실행 프로세스는 UI를 잠그고 응답하지 않게 만듭니다. 또한 자바스크립트 실행 시간에는 상한선이 있으며, 브라우저는 사용자에게 실행을 계속할지 여부를 묻습니다.
모든 이것은 정말 나쁜 사용자 경험을 유발합니다. 사용자는 모든 것이 원활하게 작동하는지 여부를 알 수 없습니다. 게다가, 느린 연결을 가진 사용자에게는 더욱 나쁜 영향이 있을 것입니다.
다음에서는 서로 상호작용하는 세 가지 다른 솔루션을 살펴볼 것입니다.
async/await와 함께하는 약속들 (ES2017+입니다. transpiler 또는 regenerator를 사용하면 구 버전 브라우저에서도 사용할 수 있습니다)
콜백 (노드에서 인기)
then()와 함께하는 약속 (ES2015+은 다양한 약속 라이브러리 중 하나를 사용하면 이전 버전의 브라우저에서도 사용 가능합니다)
현재 브라우저와 노드 7+ 모두에서 모두 사용 가능합니다.
ES2017+: async/await와 함께하는 Promises
2017년에 출시된 ECMAScript 버전은 비동기 함수를 구문 수준에서 지원하기 시작했습니다. async 와 await의 도움으로, 동기식 스타일로 비동기 코드를 작성할 수 있습니다. 코드는 여전히 비동기적이지만, 읽고 이해하기 쉬워집니다.
async/await은 약속 위에 구축되었습니다. async 함수는 항상 약속을 반환합니다. await는 약속을 해결하고 약속이 이행된 값이나 약속이 거부된 경우 에러를 발생시킵니다.
중요: await i는 async 함수 내부 또는 vaScript module . Top에서만 사용할 수 있습니다. 최상위 await i는 모듈 외부에서 지원되지 않으므로 모듈을 사용하지 않을 경우 async IIFE ( Immediately Invoked Function Expression ) )를 사용하여 async 컨텍스트를 시작해야 할 수 있습니다.
MDN에서 async과 await에 대해 더 읽을 수 있습니다.
위의 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);
})();
현재 browser 및 node 버전은 async/await를 지원합니다. regenerator의 도움을 받아 코드를 ES5로 변경하여 이전 환경도 지원할 수 있습니다 (또는 regenerator를 사용하는 도구, 예: Babel).
함수가 콜백을 받아들이게 하세요.
콜백은 함수 1이 함수 2에 전달될 때 발생합니다. 함수 2는 준비되어 있을 때마다 함수 1을 호출할 수 있습니다. 비동기적인 프로세스의 경우, 콜백은 비동기적인 프로세스가 완료되면 호출됩니다. 보통 결과는 콜백에 전달됩니다.
질문의 예시에서, foo 에게 콜백을 받아들이고 success 콜백으로 사용할 수 있습니다. 따라서 이것은...
var result = foo();
// Code that depends on 'result'
됩니다
foo(function(result) {
// Code that depends on 'result'
});
여기서 우리는 inline 함수를 정의했지만 어떤 함수 참조도 전달할 수 있습니다.
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo은 다음과 같이 정의됩니다:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback는 우리가 호출할 때 전달하는 함수를 가리킬 것이고, 우리는 이것을 # $!* && * #$&에 전달하고 # $$! &! ^$$&로 전달합니다. 즉, Ajax 요청이 성공하면 ajax will가 callback를 호출하고 콜백에 응답을 전달합니다 (이것은 우리가 콜백을 정의한 방식이기 때문에 result로 참조할 수 있습니다).
콜백에 전달하기 전에 응답을 처리할 수도 있습니다.
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
콜백을 사용하여 코드를 작성하는 것은 생각하는 것보다 쉽습니다. 결국 브라우저에서의 JavaScript는 이벤트 중심적입니다(DOM 이벤트). Ajax 응답을 수신하는 것은 이벤트일 뿐입니다.
제 3자 코드와 함께 작업해야하는 경우 어려움이 발생할 수 있지만, 어플리케이션 흐름을 신중하게 생각하면 대부분의 문제를 해결할 수 있습니다.
ES2015+: then()와 함께하는 Promises
Promise API은 ECMAScript 6 (ES2015)의 새로운 기능입니다. 하지만 그것은 이미 좋은 browser support을 가지고 있습니다. 또한 많은 라이브러리들이 표준 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; }
이 답변에서는 Promise가 제공하는 모든 장점을 설명하는 것은 범위를 벗어납니다. 그러나 새 코드를 작성하면 심각하게 고려해야 합니다. 그들은 당신의 코드를 큰 추상화와 분리를 제공합니다.
약속에 대한 더 많은 정보: HTML5 rocks - JavaScript Promises.
사이드 노트: jQuery의 지연된 객체
Deferred objects는 jQuery의 프로미스의 사용자 정의 구현입니다 (Promise API가 표준화되기 전에). 거의 프로미스처럼 동작하지만 조금 다른 API를 노출합니다.
제이쿼리의 모든 Ajax 메소드는 이미 deferred object (사실은 deferred object의 promise)를 반환하며, 이를 함수에서 그대로 반환할 수 있습니다.
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
부가 설명: 약속의 함정들
약속(Promise)과 지연된 객체(deferred object)는 미래 값에 대한 컨테이너일 뿐이며, 값 자체는 아님을 유념하세요. 예를 들어, 다음과 같은 경우를 가정해보세요:
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를 사용하면 async 옵션을 false로 설정할 수 있습니다. 이 옵션은 jQuery 1.8 이후로 사용되지 않습니다. 그런 다음 success 콜백을 여전히 사용하거나 jqXHR object의 속성에 액세스할 수 있습니다.
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
만약 $.get나 $.getJSON 등 다른 jQuery Ajax 메서드를 사용하면, $.ajax로 변경해야 합니다 (왜냐하면 $.ajax에는 구성 매개변수만 전달할 수 있기 때문입니다).
주의하세요! 동기식 JSONP 요청을 만들 수 없습니다. JSONP의 본질적 특성상 항상 비동기식입니다 (이러한 이유 중 하나로 이 옵션을 심지어 고려하지 않아야합니다).
답변 2
비동기 호출에서 응답을 반환하는 방법에 대해
웹 개발에서 비동기 호출(Asynchronous call)이란 동기적인 처리방식과 달리, 요청과 처리가 독립적으로 일어나는 것을 말합니다. 이 방식을 사용하면 웹 페이지가 갱신되거나 자바스크립트 함수가 실행될 때 브라우저가 멈추지 않고 다른 작업을 수행할 수 있습니다. 그러나 비동기 호출을 사용할 때 문제가 되는 것 중 하나는 응답(Response)을 반환하는 방식입니다.
비동기 호출에서 응답을 반환하는 방법은 여러 가지가 있습니다. 그 중 가장 일반적인 방법은 콜백 함수(Callback function)를 사용하는 것입니다. 콜백 함수란 비동기 호출이 처리된 후 호출될 함수를 말합니다. 이렇게 콜백 함수를 사용하면, 비동기 호출이 완료되면 반환할 응답을 콜백 함수의 인자로 전달하면 됩니다. 이 방법은 간단하지만, 콜백 함수를 중첩해서 사용하는 경우 코드의 가독성이 떨어지고, 에러 처리도 복잡해집니다.
다른 방법으로는 Promise 객체를 사용하는 것입니다. Promise 객체는 콜백 함수를 사용하지 않고 비동기 호출의 결과를 다룰 수 있는 객체입니다. Promise 객체는 Pending, Fulfilled, Rejected 세 가지 상태를 가지며, 호출이 완료됐을 때 then() 메소드를 사용하여 성공 콜백 함수와 실패 콜백 함수를 처리합니다. 이 방법은 가독성이 좋고, 에러 처리도 간단합니다.
마지막으로는 async/await 문법을 사용하는 것입니다. async/await는 Promise를 좀 더 간단하게 사용할 수 있는 문법입니다. async 함수를 만들고, 내부에서 await 키워드를 사용하여 비동기 호출이 완료될 때까지 기다리고, 호출 결과를 반환합니다. 이 방법은 가독성도 좋고, Promise보다 더 간단합니다.
비동기 호출에서 응답을 처리하는 방법은 다양합니다. 그러나 이 방법들 중 하나를 사용하면, 비동기 호출에서 반환된 응답을 간단하게 처리할 수 있습니다. 따라서 개발자들은 익숙하고 편리한 방법을 찾아 사용하면 됩니다.