-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove second setImmediate wrapper around callback during action exec…
…ution, for less artifial yielding for most of the normal cases where callback function executes successfully with no exception. (#537)
- Loading branch information
Showing
6 changed files
with
245 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/* jshint newcap:false */ | ||
/* global describe, it, beforeEach, before, after */ | ||
|
||
'use strict'; | ||
|
||
var expect = require('chai').expect; | ||
var promiseCallback = require('../../../utils/promiseCallback'); | ||
|
||
var originalSetImmediate = global.setImmediate; | ||
var setImmediateFuncNames = []; | ||
|
||
function customSetImmediate() { | ||
setImmediateFuncNames.push(arguments[0].name); | ||
originalSetImmediate.apply(null, arguments); | ||
} | ||
|
||
describe('#promiseCallback', function () { | ||
before(function() { | ||
global.setImmediate = customSetImmediate; | ||
}); | ||
|
||
beforeEach(function () { | ||
setImmediateFuncNames = []; | ||
}); | ||
|
||
after(function() { | ||
global.setImmediate = originalSetImmediate; | ||
}); | ||
|
||
describe('Regular execution', function () { | ||
it('should call callback when promise resolves', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
resolve('resolved'); | ||
}); | ||
promiseCallback(promise, function callbackFn(err, result) { | ||
expect(err).to.equal(null); | ||
expect(result).to.equal('resolved'); | ||
expect(setImmediateFuncNames.length).to.equal(1); | ||
expect(setImmediateFuncNames[0]).to.equal('callbackFn'); | ||
done(); | ||
}); | ||
}); | ||
it('should call callback when promise rejects', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
reject('rejected'); | ||
}); | ||
promiseCallback(promise, function callbackFn(err, result) { | ||
expect(err).to.equal('rejected'); | ||
expect(result).to.equal(undefined); | ||
expect(setImmediateFuncNames.length).to.equal(1); | ||
expect(setImmediateFuncNames[0]).to.equal('callbackFn'); | ||
done(); | ||
}); | ||
}); | ||
it('should not throw error from success callback in same cycle', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
resolve('resolved'); | ||
}); | ||
var caughtError = null; | ||
try { | ||
promiseCallback(promise, function callbackFn(err, result) { | ||
throw new Error('callback error'); | ||
}); | ||
} catch (e) { | ||
caughtError = e; | ||
} | ||
originalSetImmediate(function () { | ||
expect(setImmediateFuncNames.length).to.equal(1); | ||
expect(setImmediateFuncNames[0]).to.equal('callbackFn'); | ||
expect(caughtError).to.equal(null); | ||
done(); | ||
}); | ||
}); | ||
it('should not throw error from failure callback in same cycle', function (done) { | ||
var promise = new Promise(function callbackFn(resolve, reject) { | ||
reject('rejected'); | ||
}); | ||
var caughtError = null; | ||
try { | ||
promiseCallback(promise, function callbackFn(err, result) { | ||
if (err) { | ||
throw new Error('callback error'); | ||
} | ||
}); | ||
} catch (e) { | ||
caughtError = e; | ||
} | ||
originalSetImmediate(function () { | ||
expect(setImmediateFuncNames.length).to.equal(1); | ||
expect(setImmediateFuncNames[0]).to.equal('callbackFn'); | ||
expect(caughtError).to.equal(null); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Optimized execution', function () { | ||
it('should call callback when promise resolves', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
resolve('resolved'); | ||
}); | ||
promiseCallback(promise, function (err, result) { | ||
expect(err).to.equal(null); | ||
expect(result).to.equal('resolved'); | ||
expect(setImmediateFuncNames.length).to.equal(0, | ||
'no setImmediate for successful callback'); | ||
done(); | ||
}, { | ||
optimize: true | ||
}); | ||
}); | ||
it('should call callback when promise rejects', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
reject('rejected'); | ||
}); | ||
promiseCallback(promise, function (err, result) { | ||
expect(err).to.equal('rejected'); | ||
expect(result).to.equal(undefined); | ||
expect(setImmediateFuncNames.length).to.equal(0, | ||
'no setImmediate for successful callback'); | ||
done(); | ||
}, { | ||
optimize: true | ||
}); | ||
}); | ||
it('should not throw error from success callback in same cycle', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
resolve('resolved'); | ||
}); | ||
var caughtError = null; | ||
try { | ||
promiseCallback(promise, function callbackFn(err, result) { | ||
throw new Error('callback error'); | ||
}, { | ||
optimize: true | ||
}); | ||
} catch (e) { | ||
caughtError = e; | ||
} | ||
originalSetImmediate(function () { | ||
expect(setImmediateFuncNames.length).to.equal(1, | ||
'1 setImmediate for bad callback'); | ||
expect(setImmediateFuncNames[0]).to.equal('doNotSwallowError'); | ||
expect(caughtError).to.equal(null); | ||
done(); | ||
}); | ||
}); | ||
it('should not throw error from failure callback in same cycle', function (done) { | ||
var promise = new Promise(function (resolve, reject) { | ||
reject('rejected'); | ||
}); | ||
var caughtError = null; | ||
try { | ||
promiseCallback(promise, function callbackFn(err, result) { | ||
if (err) { | ||
throw new Error('callback error'); | ||
} | ||
}, { | ||
optimize: true | ||
}); | ||
} catch (e) { | ||
caughtError = e; | ||
} | ||
originalSetImmediate(function () { | ||
expect(setImmediateFuncNames.length).to.equal(1, | ||
'1 setImmediate for bad callback'); | ||
expect(setImmediateFuncNames[0]).to.equal('doNotSwallowError'); | ||
expect(caughtError).to.equal(null); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
'use strict'; | ||
|
||
require('setimmediate'); | ||
|
||
/** | ||
* Execute a callback function when promise resolves or rejects | ||
* @method promiseCallback | ||
* @param {Promise} promise The promise | ||
* @param {Function} callbackFn The callback function | ||
* @param {Object} options The options object | ||
* @param {Boolean} options.optimize Whether to optimize | ||
* @return {void} | ||
*/ | ||
function promiseCallback (promise, callbackFn, options) { | ||
if (!promise || typeof callbackFn !== 'function') { | ||
return; | ||
} | ||
|
||
if (options && options.optimize) { | ||
promise.then(function (result) { | ||
callbackFn(null, result); | ||
}, callbackFn) | ||
['catch'](function (err) { | ||
// Ensures that thrown errors in the `callbackFn()` callback above are | ||
// not swallowed by promise | ||
setImmediate(function doNotSwallowError() { | ||
throw err; | ||
}); | ||
}); | ||
} else { | ||
promise.then(function(result) { | ||
// Ensures that errors in callback are not swallowed by promise | ||
setImmediate(callbackFn, null, result); | ||
}, function (err) { | ||
// Ensures that errors in callback are not swallowed by promise | ||
setImmediate(callbackFn, err); | ||
}); | ||
} | ||
}; | ||
|
||
module.exports = promiseCallback; |