Skip to content
This repository was archived by the owner on Feb 24, 2018. It is now read-only.
This repository was archived by the owner on Feb 24, 2018. It is now read-only.

Async APIs should be returning Promises #88

@simonbuchan

Description

@simonbuchan

The current callbacks are a mess of node-style and callback objects, and are often immediately invoked, a problem for re-entrancy. I propose all callback accepting methods are updated to return a Promise and to make the callback argument optional.

With all the current lib dependencies, adding a dependency for global Promise or polyfill should not be a big deal.

There is a growing expectation that async methods return a Promise, they compose far better, and the proposed (and available now with babel) async / await language feature make them as simple to use as synchronous methods:

// callback style, still works function getUserEmailCallback(cognitoUser, callback) { cognitoUser.getUserAttributes(function(err, result) { if (err) { return callback(err, null); } var attr = result.filter(function (x) { return x.getName() === 'email'; })[0]; if (!attr) { return callback(new Error('User missing "email" attribute?'), null); } callback(null, attr.getValue()); }); } // promise style function getUserEmailPromise(cognitoUser) { return cognitoUser.getUserAttributes().then(function (result) { var attr = result.filter(function (x) { return x.getName() === 'email'; })[0]; if (!attr) { throw new Error('User missing "email" attribute?'); } return attr.getValue(); }); } // promise style used with async function language feature async function getUserEmailAsync(cognitoUser) { var result = await cognitoUser.getUserAttributes(); var attr = result.filter(function (x) { return x.getName() === 'email'; })[0]; if (!attr) { throw new Error('User missing "email" attribute?'); } return attr.getValue(); }

With callback object styles, I currently only propose making onSuccess and onFailure optional, other callback properties can still make sense (but probably should be broken into multiple calls later):

function provideEmailVerificationCallbackl(cognitoUser, verificationCode, callback) { cognitoUser.getAttributeVerificationCode('email', { onSuccess: function (result) { callback(null, result); }, onFailure: function(err) { callback(err, null); }, inputVerificationCode() { cognitoUser.verifyAttribute('email', verificationCode, this); } }); } function provideEmailVerificationPromise(cognitoUser, verificationCode) { return cognitoUser.getAttributeVerificationCode('email', { inputVerificationCode() { cognitoUser.verifyAttribute('email', verificationCode, this); } }); }

OLD PRE-ES6 CODE Strawman implementations (untested)

A minimal (hand-written) diff for hybrid node style and Promise returning:

 CognitoUser.prototype.deleteUser = function deleteUser(callback) { + var promise = new Promise(function (resolve, reject) { if (this.signInUserSession != null && this.signInUserSession.isValid()) { this.client.deleteUser({ AccessToken : this.signInUserSession.getAccessToken().getJwtToken() }, function (err, data) { if (err) { - return callback(err, null); + return reject(err); } else { - return callback(null, 'SUCCESS'); + return resolve('SUCCESS'); } }); } else { - return callback(new Error('User is not authenticated'), null); + throw new Error('User is not authenticated'); } + }); + if (callback) { + promise.then(function (data) { callback(null, data); }, function (err) { callback(err, null); }); + } + return promise; };

Simply wrapping with polygoat:

- CognitoUser.prototype.deleteUser = function deleteUser(callback) { + CognitoUser.prototype.deleteUser = function deleteUser(userCallback) { + return polygoat(function (callback) { if (this.signInUserSession != null && this.signInUserSession.isValid()) { this.client.deleteUser({ AccessToken : this.signInUserSession.getAccessToken().getJwtToken() }, function (err, data) { if (err) { return callback(err, null); } else { return callback(null, 'SUCCESS'); } }); } else { return callback(new Error('User is not authenticated'), null); } + }, userCallback); };

Rewriting using AWS SDK promise support and hybrid-izer wrapper:

 CognitoUser.prototype.deleteUser = toNodePromiseHybrid(function deleteUser() { if (this.signInUserSession != null && this.signInUserSession.isValid()) { return this.client.deleteUser({ AccessToken : this.signInUserSession.getAccessToken().getJwtToken() }).promise(); } else { throw new Error('User is not authenticated'); } }); function toNodePromiseHybrid(method) { return function () { var args = Array.prototype.slice(arguments); var callback = null; if (args.length && args[args.length - 1] instanceof Function) { callback = args.pop(); } var promise = method.apply(this, args); if (callback) { promise.then(function (data) { callback(null, data); }, function (err) { callback(err, null); }); } return promise; }; }

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions