summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraeme Yeates <yeatesgraeme@gmail.com>2016-05-05 10:07:32 -0400
committerGraeme Yeates <yeatesgraeme@gmail.com>2016-05-05 10:07:32 -0400
commit0f1f2844ab4931188f378badbf401573b46b72e1 (patch)
tree6d0fe106b1d81726afe40439e169491ea27d8b2a
parent19585c1717f9694ef685af4adf1c96835c3ddfba (diff)
parentd9a03388149e2dbfb23a3095910a707a836ca5d2 (diff)
downloadasync-0f1f2844ab4931188f378badbf401573b46b72e1.tar.gz
Merge pull request #1142 from ezubarev/master
Make once and onlyOnce exception safe
-rw-r--r--lib/internal/once.js3
-rw-r--r--lib/internal/onlyOnce.js3
-rw-r--r--mocha_test/map.js311
-rwxr-xr-xtest/test-async.js282
4 files changed, 315 insertions, 284 deletions
diff --git a/lib/internal/once.js b/lib/internal/once.js
index 11678dc..f601185 100644
--- a/lib/internal/once.js
+++ b/lib/internal/once.js
@@ -1,7 +1,8 @@
export default function once(fn) {
return function () {
if (fn === null) return;
- fn.apply(this, arguments);
+ var callFn = fn;
fn = null;
+ callFn.apply(this, arguments);
};
}
diff --git a/lib/internal/onlyOnce.js b/lib/internal/onlyOnce.js
index f4241c8..355ff41 100644
--- a/lib/internal/onlyOnce.js
+++ b/lib/internal/onlyOnce.js
@@ -3,7 +3,8 @@
export default function onlyOnce(fn) {
return function() {
if (fn === null) throw new Error("Callback was already called.");
- fn.apply(this, arguments);
+ var callFn = fn;
fn = null;
+ callFn.apply(this, arguments);
};
}
diff --git a/mocha_test/map.js b/mocha_test/map.js
new file mode 100644
index 0000000..5161765
--- /dev/null
+++ b/mocha_test/map.js
@@ -0,0 +1,311 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+var assert = require('assert');
+
+describe("map", function() {
+
+ function mapIteratee(call_order, x, callback) {
+ setTimeout(function() {
+ call_order.push(x);
+ callback(null, x * 2);
+ }, x * 25);
+ }
+
+ it('basic', function(done) {
+ var call_order = [];
+ async.map([1, 3, 2], mapIteratee.bind(this, call_order), function(err, results) {
+ assert(err === null, err + " passed instead of 'null'");
+ expect(call_order).to.eql([1, 2, 3]);
+ expect(results).to.eql([2, 6, 4]);
+ done();
+ });
+ });
+
+ it('with reflect', function(done) {
+ var call_order = [];
+ async.map([1, 3, 2], async.reflect(function(item, cb) {
+ setTimeout(function() {
+ call_order.push(item);
+ cb(null, item * 2);
+ }, item * 25);
+ }), function(err, results) {
+ assert(err === null, err + " passed instead of 'null'");
+ expect(call_order).to.eql([1, 2, 3]);
+ expect(results).to.eql([{
+ value: 2
+ }, {
+ value: 6
+ }, {
+ value: 4
+ }]);
+ done();
+ });
+ });
+
+ it('error with reflect', function(done) {
+ var call_order = [];
+ async.map([-1, 1, 3, 2], async.reflect(function(item, cb) {
+ setTimeout(function() {
+ call_order.push(item);
+ if (item < 0) {
+ cb('number less then zero');
+ } else {
+ cb(null, item * 2);
+ }
+ }, item * 25);
+ }), function(err, results) {
+ assert(err === null, err + " passed instead of 'null'");
+ expect(call_order).to.eql([-1, 1, 2, 3]);
+ expect(results).to.eql([{
+ error: 'number less then zero'
+ }, {
+ value: 2
+ }, {
+ value: 6
+ }, {
+ value: 4
+ }]);
+ done();
+ });
+ });
+
+ it('map original untouched', function(done) {
+ var a = [1, 2, 3];
+ async.map(a, function(x, callback) {
+ callback(null, x * 2);
+ }, function(err, results) {
+ expect(results).to.eql([2, 4, 6]);
+ expect(a).to.eql([1, 2, 3]);
+ done();
+ });
+ });
+
+ it('map without main callback', function(done) {
+ var a = [1, 2, 3];
+ var r = [];
+ async.map(a, function(x, callback) {
+ r.push(x);
+ var done_ = r.length == a.length;
+ callback(null);
+ if (done_) {
+ expect(r).to.eql(a);
+ done();
+ }
+ });
+ });
+
+ it('map error', function(done) {
+ async.map([1, 2, 3], function(x, callback) {
+ callback('error');
+ }, function(err) {
+ expect(err).to.equal('error');
+ });
+ setTimeout(done, 50);
+ });
+
+ it('map undefined array', function(done) {
+ async.map(undefined, function(x, callback) {
+ callback();
+ }, function(err, result) {
+ expect(err).to.equal(null);
+ expect(result).to.eql([]);
+ });
+ setTimeout(done, 50);
+ });
+
+ it('map object', function(done) {
+ async.map({
+ a: 1,
+ b: 2,
+ c: 3
+ }, function(val, callback) {
+ callback(null, val * 2);
+ }, function(err, result) {
+ if (err) throw err;
+ expect(Object.prototype.toString.call(result)).to.equal('[object Object]');
+ expect(result).to.eql({
+ a: 2,
+ b: 4,
+ c: 6
+ });
+ done();
+ });
+ });
+
+ it('mapSeries', function(done) {
+ var call_order = [];
+ async.mapSeries([1, 3, 2], mapIteratee.bind(this, call_order), function(err, results) {
+ assert(err === null, err + " passed instead of 'null'");
+ expect(call_order).to.eql([1, 3, 2]);
+ expect(results).to.eql([2, 6, 4]);
+ done();
+ });
+ });
+
+ it('mapSeries error', function(done) {
+ async.mapSeries([1, 2, 3], function(x, callback) {
+ callback('error');
+ }, function(err) {
+ expect(err).to.equal('error');
+ });
+ setTimeout(done, 50);
+ });
+
+ it('mapSeries undefined array', function(done) {
+ async.mapSeries(undefined, function(x, callback) {
+ callback();
+ }, function(err, result) {
+ expect(err).to.equal(null);
+ expect(result).to.eql([]);
+ });
+ setTimeout(done, 50);
+ });
+
+ it('mapSeries object', function(done) {
+ async.mapSeries({
+ a: 1,
+ b: 2,
+ c: 3
+ }, function(val, callback) {
+ callback(null, val * 2);
+ }, function(err, result) {
+ if (err) throw err;
+ expect(result).to.eql({
+ a: 2,
+ b: 4,
+ c: 6
+ });
+ done();
+ });
+ });
+
+ it('mapLimit', function(done) {
+ var call_order = [];
+ async.mapLimit([2, 4, 3], 2, mapIteratee.bind(this, call_order), function(err, results) {
+ assert(err === null, err + " passed instead of 'null'");
+ expect(call_order).to.eql([2, 4, 3]);
+ expect(results).to.eql([4, 8, 6]);
+ done();
+ });
+ });
+
+ it('mapLimit empty array', function(done) {
+ async.mapLimit([], 2, function(x, callback) {
+ test.ok(false, 'iteratee should not be called');
+ callback();
+ }, function(err) {
+ if (err) throw err;
+ assert(true, 'should call callback');
+ });
+ setTimeout(done, 25);
+ });
+
+ it('mapLimit undefined array', function(done) {
+ async.mapLimit(undefined, 2, function(x, callback) {
+ callback();
+ }, function(err, result) {
+ expect(err).to.equal(null);
+ expect(result).to.eql([]);
+ });
+ setTimeout(done, 50);
+ });
+
+ it('mapLimit limit exceeds size', function(done) {
+ var call_order = [];
+ async.mapLimit([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 20, mapIteratee.bind(this, call_order), function(err, results) {
+ expect(call_order).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ expect(results).to.eql([0, 2, 4, 6, 8, 10, 12, 14, 16, 18]);
+ done();
+ });
+ });
+
+ it('mapLimit limit equal size', function(done) {
+ var call_order = [];
+ async.mapLimit([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10, mapIteratee.bind(this, call_order), function(err, results) {
+ expect(call_order).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ expect(results).to.eql([0, 2, 4, 6, 8, 10, 12, 14, 16, 18]);
+ done();
+ });
+ });
+
+ it('mapLimit zero limit', function(done) {
+ async.mapLimit([0, 1, 2, 3, 4, 5], 0, function(x, callback) {
+ test.ok(false, 'iteratee should not be called');
+ callback();
+ }, function(err, results) {
+ expect(results).to.eql([]);
+ assert(true, 'should call callback');
+ });
+ setTimeout(done, 25);
+ });
+
+ it('mapLimit error', function(done) {
+ var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ var call_order = [];
+
+ async.mapLimit(arr, 3, function(x, callback) {
+ call_order.push(x);
+ if (x === 2) {
+ callback('error');
+ }
+ }, function(err) {
+ expect(call_order).to.eql([0, 1, 2]);
+ expect(err).to.equal('error');
+ });
+ setTimeout(done, 25);
+ });
+
+ it('mapLimit does not continue replenishing after error', function(done) {
+ var started = 0;
+ var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ var delay = 10;
+ var limit = 3;
+ var maxTime = 10 * arr.length;
+
+ async.mapLimit(arr, limit, function(x, callback) {
+ started++;
+ if (started === 3) {
+ return callback(new Error("Test Error"));
+ }
+ setTimeout(function() {
+ callback();
+ }, delay);
+ }, function() {});
+
+ setTimeout(function() {
+ expect(started).to.equal(3);
+ done();
+ }, maxTime);
+ });
+
+ it('map with Map', function(done) {
+ if (typeof Map !== 'function')
+ return done();
+
+ var map = new Map();
+ map.set(1, "a");
+ map.set(2, "b");
+ async.map(map, function(val, cb) {
+ cb(null, val);
+ }, function(err, result) {
+ assert(Array.isArray(result), "map should return an array for an iterable");
+ done();
+ });
+ });
+
+ // Issue 1106 on github: https://github.com/caolan/async/issues/1106
+ it('map main callback is called only once', function(done) {
+ async.map([1, 2], function(item, callback) {
+ try {
+ callback(item);
+ } catch (exception) {
+ expect(function() {
+ callback(exception);
+ }).to.throw(/already called/);
+ done();
+ }
+ }, function(err) {
+ no_such_function();
+ });
+ });
+});
diff --git a/test/test-async.js b/test/test-async.js
index b310f70..53d8cb4 100755
--- a/test/test-async.js
+++ b/test/test-async.js
@@ -30,13 +30,6 @@ function forEachOfIteratee(args, value, key, callback) {
}, value*25);
}
-function mapIteratee(call_order, x, callback) {
- setTimeout(function(){
- call_order.push(x);
- callback(null, x*2);
- }, x*25);
-}
-
function eachNoCallbackIteratee(test, x, callback) {
test.equal(x, 1);
callback();
@@ -1311,281 +1304,6 @@ exports['forEachOfLimit with Map (iterators)'] = function(test){
});
};
-exports['map'] = {
-
- 'basic': function(test){
- var call_order = [];
- async.map([1,3,2], mapIteratee.bind(this, call_order), function(err, results){
- test.ok(err === null, err + " passed instead of 'null'");
- test.same(call_order, [1,2,3]);
- test.same(results, [2,6,4]);
- test.done();
- });
-},
-
- 'with reflect': function(test){
- var call_order = [];
- async.map([1,3,2], async.reflect(function(item, cb) {
- setTimeout(function(){
- call_order.push(item);
- cb(null, item*2);
- }, item*25);
- }), function(err, results){
- test.ok(err === null, err + " passed instead of 'null'");
- test.same(call_order, [1,2,3]);
- test.same(results, [
- { value: 2 },
- { value: 6 },
- { value: 4 }
- ]);
- test.done();
- });
-},
-
- 'error with reflect': function(test){
- var call_order = [];
- async.map([-1,1,3,2], async.reflect(function(item, cb) {
- setTimeout(function(){
- call_order.push(item);
- if (item < 0) {
- cb('number less then zero');
- } else {
- cb(null, item*2);
- }
-
- }, item*25);
- }), function(err, results){
- test.ok(err === null, err + " passed instead of 'null'");
- test.same(call_order, [-1,1,2,3]);
- test.same(results, [
- { error: 'number less then zero' },
- { value: 2 },
- { value: 6 },
- { value: 4 }
- ]);
- test.done();
- });
-},
-
- 'map original untouched': function(test){
- var a = [1,2,3];
- async.map(a, function(x, callback){
- callback(null, x*2);
- }, function(err, results){
- test.same(results, [2,4,6]);
- test.same(a, [1,2,3]);
- test.done();
- });
-},
-
- 'map without main callback': function(test){
- var a = [1,2,3];
- var r = [];
- async.map(a, function(x, callback){
- r.push(x);
- var done = r.length == a.length;
- callback(null);
- if (done) {
- test.same(r, a);
- test.done();
- }
- });
-},
-
- 'map error': function(test){
- test.expect(1);
- async.map([1,2,3], function(x, callback){
- callback('error');
- }, function(err){
- test.equals(err, 'error');
- });
- setTimeout(test.done, 50);
-},
-
- 'map undefined array': function(test){
- test.expect(2);
- async.map(undefined, function(x, callback){
- callback();
- }, function(err, result){
- test.equals(err, null);
- test.same(result, []);
- });
- setTimeout(test.done, 50);
-},
-
- 'map object': function (test) {
- async.map({a: 1, b: 2, c: 3}, function (val, callback) {
- callback(null, val * 2);
- }, function (err, result) {
- if (err) throw err;
- test.equals(Object.prototype.toString.call(result), '[object Object]');
- test.same(result, {a: 2, b: 4, c: 6});
- test.done();
- });
-},
-
- 'mapSeries': function(test){
- var call_order = [];
- async.mapSeries([1,3,2], mapIteratee.bind(this, call_order), function(err, results){
- test.ok(err === null, err + " passed instead of 'null'");
- test.same(call_order, [1,3,2]);
- test.same(results, [2,6,4]);
- test.done();
- });
-},
-
- 'mapSeries error': function(test){
- test.expect(1);
- async.mapSeries([1,2,3], function(x, callback){
- callback('error');
- }, function(err){
- test.equals(err, 'error');
- });
- setTimeout(test.done, 50);
-},
-
- 'mapSeries undefined array': function(test){
- test.expect(2);
- async.mapSeries(undefined, function(x, callback){
- callback();
- }, function(err, result){
- test.equals(err, null);
- test.same(result, []);
- });
- setTimeout(test.done, 50);
-},
-
- 'mapSeries object': function (test) {
- async.mapSeries({a: 1, b: 2, c: 3}, function (val, callback) {
- callback(null, val * 2);
- }, function (err, result) {
- if (err) throw err;
- test.same(result, {a: 2, b: 4, c: 6});
- test.done();
- });
-},
-
- 'mapLimit': function(test){
- var call_order = [];
- async.mapLimit([2,4,3], 2, mapIteratee.bind(this, call_order), function(err, results){
- test.ok(err === null, err + " passed instead of 'null'");
- test.same(call_order, [2,4,3]);
- test.same(results, [4,8,6]);
- test.done();
- });
-},
-
- 'mapLimit empty array': function(test){
- test.expect(1);
- async.mapLimit([], 2, function(x, callback){
- test.ok(false, 'iteratee should not be called');
- callback();
- }, function(err){
- if (err) throw err;
- test.ok(true, 'should call callback');
- });
- setTimeout(test.done, 25);
-},
-
- 'mapLimit undefined array': function(test){
- test.expect(2);
- async.mapLimit(undefined, 2, function(x, callback){
- callback();
- }, function(err, result){
- test.equals(err, null);
- test.same(result, []);
- });
- setTimeout(test.done, 50);
-},
-
- 'mapLimit limit exceeds size': function(test){
- var call_order = [];
- async.mapLimit([0,1,2,3,4,5,6,7,8,9], 20, mapIteratee.bind(this, call_order), function(err, results){
- test.same(call_order, [0,1,2,3,4,5,6,7,8,9]);
- test.same(results, [0,2,4,6,8,10,12,14,16,18]);
- test.done();
- });
-},
-
- 'mapLimit limit equal size': function(test){
- var call_order = [];
- async.mapLimit([0,1,2,3,4,5,6,7,8,9], 10, mapIteratee.bind(this, call_order), function(err, results){
- test.same(call_order, [0,1,2,3,4,5,6,7,8,9]);
- test.same(results, [0,2,4,6,8,10,12,14,16,18]);
- test.done();
- });
-},
-
- 'mapLimit zero limit': function(test){
- test.expect(2);
- async.mapLimit([0,1,2,3,4,5], 0, function(x, callback){
- test.ok(false, 'iteratee should not be called');
- callback();
- }, function(err, results){
- test.same(results, []);
- test.ok(true, 'should call callback');
- });
- setTimeout(test.done, 25);
-},
-
- 'mapLimit error': function(test){
- test.expect(2);
- var arr = [0,1,2,3,4,5,6,7,8,9];
- var call_order = [];
-
- async.mapLimit(arr, 3, function(x, callback){
- call_order.push(x);
- if (x === 2) {
- callback('error');
- }
- }, function(err){
- test.same(call_order, [0,1,2]);
- test.equals(err, 'error');
- });
- setTimeout(test.done, 25);
-},
-
- 'mapLimit does not continue replenishing after error': function (test) {
- var started = 0;
- var arr = [0,1,2,3,4,5,6,7,8,9];
- var delay = 10;
- var limit = 3;
- var maxTime = 10 * arr.length;
-
- async.mapLimit(arr, limit, function(x, callback) {
- started ++;
- if (started === 3) {
- return callback(new Error ("Test Error"));
- }
- setTimeout(function(){
- callback();
- }, delay);
- }, function(){});
-
- setTimeout(function(){
- test.equal(started, 3);
- test.done();
- }, maxTime);
-},
-
- 'map with Map': function(test) {
- if (typeof Map !== 'function')
- return test.done();
-
- var map = new Map();
- map.set(1, "a");
- map.set(2, "b");
- async.map(map, function(val, cb) {
- cb(null, val);
- }, function (err, result) {
- test.ok(Array.isArray(result), "map should return an array for an iterable");
- test.done();
- });
-}
-
-};
-
-
exports['reduce'] = function(test){
var call_order = [];
async.reduce([1,2,3], 0, function(a, x, callback){