Q Promise 라이브러리에는 denodeify()
와 nodeify()
함수가 존재한다. 이름에서 알 수 있듯이 denodeify()
는 node.js의 Callback 형식을 Promise 형식으로 바꾸는 함수이고 nodeify()
는 Promise 스타일을 Callback 스타일로 바꾸는 함수이다.
Q.denodeify
Q.denodeify는 작년 발표에서 간단히 소개하기도 했지만, 위에서 말한 대로 Node.js의 표준 Callback을 Promise 변환하므로 Callback만 지원하는 라이브러리랑 Promise를 섞어서 사용할 때 유용하다.
var fs = require('fs');
var read = function(path, callback) {
fs.readFile(path, function(err, data) {
callback(err, data);
});
}
위와 같은 간단한 함수가 있다고 해보자. 단순히 fs.readFile
를 호출해서 결과를 콜백으로 다시 돌려주는 함수이다. 이 read
함수를 Promise로 사용하려면 Promise 형식으로 바꾸어야 하는데 보통 Q.defer
를 사용해서 다음과 같이 바꾼다.
var fs = require('fs');
var Q = require('q');
var read = function(path) {
var deferred = Q.defer();
fs.readFile(path, function(err, data) {
if (err) { return deferred.reject(err); }
deferred.resolve(data);
});
return deferred.promise;
}
이제 Promise 형식이 되었으므로 read('./test.txt').then(function(data) {}).catch(function(error) {});
와 같이 사용할 수 있다. Q.defer
에는 makeNodeResolver같은 함수도 있지만 Q.denodeify를 사용하면 다음과 같이 간단히 바꿀 수 있다.
var fs = require('fs');
var Q = require('q');
var read = Q.denodeify(fs.readFile);
Node.js의 표준 콜백 형식인 첫 파라미터는 error
이고 두 번째 파라미터가 결과 값인 function(err, data) {}
이면 위 한 줄로 Promise로 바꿀 수 있다. 물론 비슷한 역할을 하는 Q.nfbind
, Q.nbind
, Q.nfapply
, Q.nfcall
등도 있어서 상황에 맞게 사용할 수 있다.
promise.nodeify
promise.nodeify는 반대로 Promise 함수를 콜백 형식으로 변환해 주는 역할을 한다. Q.nodeify
가 아니라 promise.nodeify
라고 쓴 이유는 Q 객체의 함수가 아니라 Q에서 반환한 Promise 객체의 함수라는 의미이다.
var fs = require('fs');
var Q = require('q');
var read = function(path, callback) {
var promise = Q.denodeify(fs.readFile);
return promise(path);
}
위와 같은 Promise 함수가 있다고 해보자. 아까와는 반대로 Promise 함수를 Callback 형식으로 사용하려면 다음과 같이 변환할 수 있다.
var fs = require('fs');
var Q = require('q');
var read = function(path, callback) {
var promise = Q.denodeify(fs.readFile);
return promise(path)
.then(function(data) {
callback(null, data);
})
.catch(function(err) {
callback(err);
});
}
read('./test.txt', function(err, data) {
if (err) { return console.error(err); }
console.log(dta);
});
이렇게 콜백으로 강제로 변환하면 실제로는 잘 동작하는 것 같지만, 오류처리에 문제가 좀 있다.(Promise를 많이 사용하는 편은 아니라서 이 부분에서 고생을 좀 했다.) Promise의 then()
에서 정상 콜백을 호출하고 catch()
에서는 오류 콜백을 호출하면 일반 콜백처럼 인터페이스를 유지할 수 있지만, 문제는 호출된 콜백에서 오류가 생기게 되면 다시 원래의 Promise에서 catch
부분이 받아서 다시 콜백을 호출하게 된다. 그래서 콜백이 결과적으로는 2번 호출되게 된다.
var fs = require('fs');
var Q = require('q');
var read = function(path, callback) {
var promise = Q.denodeify(fs.readFile);
return promise(path)
.then(function(data) {
return data;
})
.catch(function(err) {
return err;
})
.nodeify(callback);
}
read('./test.txt', function(err, data) {
if (err) { return console.error(err); }
console.log(data);
});
.nodeify(callback)
는 Promise 객체에서 사용할 수 있으므로 Promise의 체이닝에 바로 연결할 수 있다. 체이닝에 nodeify()
함수를 연결하고 인자로 호출할 콜백을 전달하면 된다. 그리고 앞에 then()
이나 catch()
가 있는 경우에는 값을 retrun
해주어야 콜백에서 정상적으로 받을 수 있다. 리턴하지 않으면 다음 체이닝으로 연결되는 값이 없으므로 콜백에 인자도 전달되지 않는다. 이렇게 nodeify()
를 사용했을 경우에는 콜백에서 오류가 발견한다고 다시 Promise로 돌아가지 않는다.
그리고 이 경우에는 Promise 객체를 리턴했으므로 호출하는 쪽에서 Promise 형식과 Callback 형식을 동시에 사용할 수 있다. 그래서 위처럼 read()
함수를 콜백형태로 사용하는 대신 다음과 같이 Promise 형식으로 사용해도 아무런 문제가 없다.
read('./test.txt')
.then(function(data) {})
.catch(function(error) {});
Comments