node.js를 사용하여 콜백이 호출 될 때까지 함수를 대기시키는 방법


266

다음과 같은 단순화 된 기능이 있습니다.

function(query) {
  myApi.exec('SomeCommand', function(response) {
    return response;
  });
}

기본적으로 나는 그것을 호출 myApi.exec하고 콜백 람다에 주어진 응답을 반환하고 싶다 . 그러나 위의 코드는 작동하지 않으며 즉시 반환됩니다.

매우 hackish 시도를 위해, 나는 작동하지 않는 아래를 시도했지만 적어도 내가 달성하려고하는 아이디어를 얻습니다.

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  while (!r) {}
  return r;
}

기본적으로 이것에 대해 좋은 'node.js / event driven'방법은 무엇입니까? 콜백이 호출 될 때까지 함수가 기다린 다음 전달 된 값을 반환하고 싶습니다.


3
아니면 여기서 완전히 잘못된 방향으로 가고 있습니까? 응답을 반환하지 않고 다른 콜백을 호출해야합니까?
Chris

이것은 바쁜 루프가 작동하지 않는 이유 에 대한 최고의 설명 입니다.
bluenote10

기다리려고하지 마십시오. 콜백 종료시 다음 함수 (콜백에 따라 다름)를 호출하십시오
Atul

답변:


282

이것을하는 "good node.js / event driven"방법은 기다리지 않는 것 입니다.

노드와 같은 이벤트 중심 시스템으로 작업 할 때 거의 모든 것과 마찬가지로 함수는 계산이 완료되면 호출되는 콜백 매개 변수를 받아 들여야합니다. 호출자는 정상적인 의미에서 값이 "반환"될 때까지 기다리지 말고 결과 값을 처리 할 루틴을 보내십시오.

function(query, callback) {
  myApi.exec('SomeCommand', function(response) {
    // other stuff here...
    // bla bla..
    callback(response); // this will "return" your value to the original caller
  });
}

따라서 다음과 같이 사용하지 마십시오.

var returnValue = myFunction(query);

그러나 이렇게 :

myFunction(query, function(returnValue) {
  // use the return value here instead of like a regular (non-evented) return value
});

5
큰 확인. myApi.exec가 콜백을 호출하지 않은 경우는 어떻습니까? 콜백이 10 초 후에 호출되어 오류 값이 우리 또는 무언가를 시간 초과했다는 메시지를 표시하려면 어떻게해야합니까?
Chris

5
또는 더 나은 방법 (체크 백을 추가하여 콜백을 두 번 호출 할 수 없음) : jsfiddle.net/LdaFw/1
Jakob

148
node / js의 비 차단은 표준이지만, 차단이 필요한 경우가 있습니다 (예 : stdin에서의 차단). 노드조차도 "차단"방법이 있습니다 (모든 fs sync*방법 참조 ). 따라서 나는 이것이 여전히 유효한 질문이라고 생각합니다. 바쁜 대기를 제외하고 노드에서 블로킹을 달성하는 좋은 방법이 있습니까?
nategood

7
@nategood의 코멘트에 대한 답변 : 몇 가지 방법을 생각할 수 있습니다. 이 의견에서 설명하기에는 너무 많지만 Google에서는 설명합니다. 노드가 차단되지 않았으므로 완벽하지는 않습니다. 그것들을 제안으로 생각하십시오. 어쨌든, 여기에 간다 : (1) C를 사용하여 함수를 구현하고 NPM에 게시하여 사용하십시오. 그것이 sync방법이하는 일입니다. (2) 섬유, github.com/laverdet/node-fibers를 사용하십시오 . (3) Q 라이브러리와 같은 약속을 사용하십시오. (4) 자바 스크립트 위에 얇은 레이어를 사용하십시오. maxtaco.github.com/coffee-script
Jakob

106
사람들이 "그렇게하지 말아야합니다"라는 질문에 대답 할 때 너무 실망 스럽습니다. 도움을 원하고 질문에 대답하고 싶을 경우, 그 일을해야합니다. 그러나 내가 무언가를해서는 안된다는 것을 분명하게 말하는 것은 단지 비우호적입니다. 누군가가 동기식 또는 비동기식으로 루틴을 호출하려는 이유는 백만 가지입니다. 이 작업을 수행하는 방법에 대한 질문이었습니다. 답변을 제공하는 동안 API의 특성에 대한 유용한 조언을 제공하면 도움이되지만 답변을 제공하지 않으면 왜 귀찮게 대답합니까? (난 정말 내 자신의 조언을 머리를해야 같아요.)
하워드 Swope

46

이를 달성하는 한 가지 방법은 API 호출을 약속으로 래핑 한 다음 await결과를 기다리는 데 사용하는 것입니다.

// let's say this is the API function with two callbacks,
// one for success and the other for error
function apiFunction(query, successCallback, errorCallback) {
    if (query == "bad query") {
        errorCallback("problem with the query");
    }
    successCallback("Your query was <" + query + ">");
}

// myFunction wraps the above API call into a Promise
// and handles the callbacks with resolve and reject
function apiFunctionWrapper(query) {
    return new Promise((resolve, reject) => {
        apiFunction(query,(successResponse) => {
            resolve(successResponse);
        }, (errorResponse) => {
            reject(errorResponse)
        });
    });
}

// now you can use await to get the result from the wrapped api function
// and you can use standard try-catch to handle the errors
async function businessLogic() {
    try {
        const result = await apiFunctionWrapper("query all users");
        console.log(result);

        // the next line will fail
        const result2 = await apiFunctionWrapper("bad query");
    } catch(error) {
        console.error("ERROR:" + error);
    }
}

// call the main function
businessLogic();

산출:

Your query was <query all users>
ERROR:problem with the query

이것은 콜백으로 함수를 래핑하는 매우 잘 수행 된 예제이므로 async/await 자주 사용할 필요가 없으므로이 상황을 처리하는 방법을 기억하는 데 어려움이 있습니다. 개인 메모 / 참조 용으로 복사하고 있습니다.
robert arles


10

콜백을 사용하지 않으려면 "Q"모듈을 사용하십시오.

예를 들면 다음과 같습니다.

function getdb() {
    var deferred = Q.defer();
    MongoClient.connect(databaseUrl, function(err, db) {
        if (err) {
            console.log("Problem connecting database");
            deferred.reject(new Error(err));
        } else {
            var collection = db.collection("url");
            deferred.resolve(collection);
        }
    });
    return deferred.promise;
}


getdb().then(function(collection) {
   // This function will be called afte getdb() will be executed. 

}).fail(function(err){
    // If Error accrued. 

});

자세한 내용은 다음을 참조하십시오 : https://github.com/kriskowal/q


9

다른 코드를 실행하기 전에 노드에서 콜백 함수가 실행될 때까지 기다리는 것이 매우 간단하고 쉬운 라이브러리는 없습니다.

//initialize a global var to control the callback state
var callbackCount = 0;
//call the function that has a callback
someObj.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});
someObj2.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});

//call function that has to wait
continueExec();

function continueExec() {
    //here is the trick, wait until var callbackCount is set number of callback functions
    if (callbackCount < 2) {
        setTimeout(continueExec, 1000);
        return;
    }
    //Finally, do what you need
    doSomeThing();
}

5

참고 :이 답변은 프로덕션 코드에서 사용해서는 안됩니다. 그것은 해킹이며 그 의미에 대해 알아야합니다.

libuv 메인 이벤트 루프 (Nodejs 메인 루프)의 단일 루프 라운드를 실행할 수 있는 uvrun 모듈 ( 여기서는 최신 Nodejs 버전 용으로 업데이트 됨 )이 있습니다.

코드는 다음과 같습니다.

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  var uvrun = require("uvrun");
  while (!r)
    uvrun.runOnce();
  return r;
}

(대체로 사용할 수도 있습니다 uvrun.runNoWait(). 블로킹과 관련된 일부 문제를 피할 수 있지만 100 % CPU가 필요합니다.)

이 접근 방식은 Nodejs의 전체 목적을 무효화합니다. 즉, 모든 것이 비 동기화되고 차단되지 않습니다. 또한 콜 스택 깊이를 크게 늘릴 수 있으므로 스택 오버플로가 발생할 수 있습니다. 이러한 기능을 재귀 적으로 실행하면 문제가 발생할 수 있습니다.

코드를 "올바로"재 설계하는 방법에 대한 다른 답변을 참조하십시오.

이 솔루션은 테스트 및 esp를 수행 할 때만 유용합니다. 시리얼 코드를 동기화하고 싶습니다.


5

노드 4.8.0부터 generator라는 ES6의 기능을 사용할 수 있습니다. 더 깊은 개념을 위해이 기사 를 따를 수 있습니다 . 그러나 기본적으로 생성기를 사용 하여이 작업을 수행 할 수 있습니다. 생성기를 약속하고 관리하기 위해 블루 버드 를 사용 하고 있습니다.

아래 예제와 같이 코드가 양호해야합니다.

const Promise = require('bluebird');

function* getResponse(query) {
  const r = yield new Promise(resolve => myApi.exec('SomeCommand', resolve);
  return r;
}

Promise.coroutine(getResponse)()
  .then(response => console.log(response));

1

기능이 있다고 가정합니다.

var fetchPage(page, callback) {
   ....
   request(uri, function (error, response, body) {
        ....
        if (something_good) {
          callback(true, page+1);
        } else {
          callback(false);
        }
        .....
   });


};

다음과 같이 콜백을 사용할 수 있습니다.

fetchPage(1, x = function(next, page) {
if (next) {
    console.log("^^^ CALLBACK -->  fetchPage: " + page);
    fetchPage(page, x);
}
});

-1

비 차단 IO의 목적을 무효화합니다. 차단할 필요가 없을 때이를 차단합니다. :)

node.js가 기다리도록하는 대신 콜백을 중첩 시키거나 콜백 내에서 다른 콜백을 호출해야합니다 r.

강제 차단이 필요한 경우 아키텍처가 잘못되었다고 생각할 수 있습니다.


나는 이것을 거꾸로 의심했다.
Chris

31
아마도 http.get()일부 URL과 console.log()그 내용에 빠른 스크립트를 작성하고 싶습니다 . 노드에서 그렇게하려면 왜 뒤로 뛰어야합니까?
Dan Dascalescu

6
@ DanDascalescu : 왜 정적 언어로 유형 서명을 선언해야합니까? 그리고 왜 C와 같은 언어로 주요 메소드에 넣어야합니까? 그리고 왜 컴파일 된 언어로 컴파일해야합니까? 당신이 질문하는 것은 Node.js의 기본 디자인 결정입니다. 그 결정에는 장단점이 있습니다. 마음에 들지 않으면 자신의 스타일에 맞는 다른 언어를 사용할 수 있습니다. 그래서 우리는 둘 이상을 가지고 있습니다.
Jakob

@Jakob : 나열된 솔루션은 실제로 최적이 아닙니다. 그렇기 때문에 광섬유에서 Meteor의 서버 측 노드 사용과 같은 좋은 것이 없다는 의미는 아닙니다. 콜백 지옥 문제를 제거합니다.
Dan Dascalescu

13
@Jakob : "생태계 X가 공통 과제 Y를 불필요하게 어렵게 만드는 이유는 무엇입니까?" "만약 마음에 들지 않으면 생태계 X를 사용하지 마십시오."는 생태계 X의 설계자 및 관리자가 생태계의 실제 유용성보다 자신의 자존심을 우선시한다는 강력한 신호입니다. Ruby, Elixir 및 PHP 커뮤니티와 달리 Node 커뮤니티가 일반적인 작업을 어렵게 만드는 것은 저의 경험이었습니다. 이러한 반 패턴의 살아있는 모범으로 스스로를 제공해 주셔서 대단히 감사합니다.
재즈

-1

비동기를 사용하고 기다리는 것이 훨씬 쉽습니다.

router.post('/login',async (req, res, next) => {
i = await queries.checkUser(req.body);
console.log('i: '+JSON.stringify(i));
});

//User Available Check
async function checkUser(request) {
try {
    let response = await sql.query('select * from login where email = ?', 
    [request.email]);
    return response[0];

    } catch (err) {
    console.log(err);

  }

}

질문에 사용 된 API는 약속을 반환하지 않으므로 2 년 전에이 대답 처럼 처음에 포장해야합니다 .
Quentin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.