I’m introducing promises using the Bluebird library to my Javascript code, as they seem to be the closest available thing to the flow control combinators I’m used to in Haskell. The following Gulp task works for me:
gulp.task('upload', function(callback) {
configAWSFromFile();
var functionName = 'redacted',
Lambda = new AWS.Lambda();
Promise.promisifyAll(Object.getPrototypeOf(Lambda));
function uploadLambdaFn(lambdaFn, fileStream) {
var params = {
FunctionName: functionName,
Handler: lambdaFn.Configuration.Handler,
// snip
FunctionZip: fileStream
};
return Lambda.uploadFunctionAsync(params);
}
return Promise.join(Lambda.getFunctionAsync({FunctionName: functionName}),
fs.readFileAsync(path.join(destPrefix, uploadPackage)),
uploadLambdaFn)
.then(function(response) {
gutil.log('Response:\n' + util.inspect(response));
});
});
However, when I try to refactor the upload step out of the config object creation function, it breaks down:
gulp.task('upload', function(callback) {
configAWSFromFile();
var functionName = 'redacted',
Lambda = new AWS.Lambda();
Promise.promisifyAll(Object.getPrototypeOf(Lambda));
// rename function
function mkLambdaFnConfig(lambdaFn, fileStream) {
var params = {
FunctionName: functionName,
Handler: lambdaFn.Configuration.Handler,
// snip
FunctionZip: fileStream
};
return params; // return immediate value
}
return Promise.join(Lambda.getFunctionAsync({FunctionName: functionName}),
fs.readFileAsync(path.join(destPrefix, uploadPackage)),
Promise.method(mkLambdaFnConfig)) // wrap regular function
.then(Lambda.uploadFunctionAsync) // add the upload to the chain
.then(function(response) {
gutil.log('Response:\n' + util.inspect(response));
});
});
Gives the following stack trace (paths have been edited to be relative):
TypeError: undefined is not a function
at svc.(anonymous function) (./node_modules/aws-sdk/lib/service.js:399:21)
at tryCatcher (./node_modules/bluebird/js/main/util.js:24:31)
at ret (eval at <anonymous> (./node_modules/bluebird/js/main/promisify.js:154:12), <anonymous>:13:39)
at tryCatcher (./node_modules/bluebird/js/main/util.js:24:31)
at Promise._settlePromiseFromHandler (./node_modules/bluebird/js/main/promise.js:466:31)
at Promise._settlePromiseAt (./node_modules/bluebird/js/main/promise.js:545:18)
at Promise._settlePromises (./node_modules/bluebird/js/main/promise.js:661:14)
at Async._drainQueue (./node_modules/bluebird/js/main/async.js:79:16)
at Async._drainQueues (./node_modules/bluebird/js/main/async.js:89:10)
at Immediate.Async.drainQueues [as _onImmediate] (./node_modules/bluebird/js/main/async.js:14:14)
at processImmediate [as _immediateCallback] (timers.js:358:17)
I get an identical stack trace if I change the mkLambdaFnConfig
function to return Promise.resolve(params)
instead of wrapping mkLambdaFnConfig
in Promise.method
:
gulp.task('upload', function(callback) {
configAWSFromFile();
var functionName = 'redacted',
Lambda = new AWS.Lambda();
Promise.promisifyAll(Object.getPrototypeOf(Lambda));
// rename function
function mkLambdaFnConfig(lambdaFn, fileStream) {
var params = {
FunctionName: functionName,
Handler: lambdaFn.Configuration.Handler,
// snip
FunctionZip: fileStream
};
return Promise.resolve(params); // return already-fulfilled promise
}
return Promise.join(Lambda.getFunctionAsync({FunctionName: functionName}),
fs.readFileAsync(path.join(destPrefix, uploadPackage)),
mkLambdaFnConfig) // no additional wrapping
.then(Lambda.uploadFunctionAsync) // add the upload to the chain
.then(function(response) {
gutil.log('Response:\n' + util.inspect(response));
});
});
Generates the stack trace (again, with relative paths):
TypeError: undefined is not a function
at svc.(anonymous function) (./node_modules/aws-sdk/lib/service.js:399:21)
at tryCatcher (./node_modules/bluebird/js/main/util.js:24:31)
at ret (eval at <anonymous> (./node_modules/bluebird/js/main/promisify.js:154:12), <anonymous>:13:39)
at tryCatcher (./node_modules/bluebird/js/main/util.js:24:31)
at Promise._settlePromiseFromHandler (./node_modules/bluebird/js/main/promise.js:466:31)
at Promise._settlePromiseAt (./node_modules/bluebird/js/main/promise.js:545:18)
at Promise._settlePromises (./node_modules/bluebird/js/main/promise.js:661:14)
at Async._drainQueue (./node_modules/bluebird/js/main/async.js:79:16)
at Async._drainQueues (./node_modules/bluebird/js/main/async.js:89:10)
at Immediate.Async.drainQueues [as _onImmediate] (./node_modules/bluebird/js/main/async.js:14:14)
at processImmediate [as _immediateCallback] (timers.js:358:17)
I suspect that I’m missing some basic component of how promises work. I had thought that Promise.resolve
and Promise.method
were lifting functions like monadic return
and applicative pure
, but I’m guessing now that’s a gross misunderstanding.
Edit for future visitors:
This is the working code I ended up with (after @Bergi pointed out that it’s just a binding issue):
gulp.task('upload', function(callback) {
configAWSFromFile();
var functionName = 'redacted',
Lambda = new AWS.Lambda();
// As noted by @Esailija, this should be moved to init.
Promise.promisifyAll(Object.getPrototypeOf(Lambda));
function mkLambdaFnConfig(lambdaFn, fileStream) {
var params = {
FunctionName: functionName,
Handler: lambdaFn.Configuration.Handler,
// snip
FunctionZip: fileStream
};
return params;
}
return Promise.join(Lambda.getFunctionAsync({FunctionName: functionName}),
fs.readFileAsync(path.join(destPrefix, uploadPackage)),
Promise.method(mkLambdaFnConfig)) // wrap synchronous function
// Methods added as callbacks need to be bound, just like usual!
.then(Lambda.uploadFunctionAsync.bind(Lambda))
.then(function(response) {
gutil.log('Response:\n' + util.inspect(response));
});
});