From a1b8d048963e99e9a9ff5c4a570a103707e303ba Mon Sep 17 00:00:00 2001 From: Zubarev Evgeny Date: Wed, 4 May 2016 20:34:12 +0600 Subject: Make once and onlyOnce exception safe More details in issue#1106 --- lib/internal/once.js | 3 ++- lib/internal/onlyOnce.js | 3 ++- 2 files changed, 4 insertions(+), 2 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); }; } -- cgit v1.2.1 From e66042c60ea7fe15a0bc48536fc6ee97b3f9d324 Mon Sep 17 00:00:00 2001 From: ezubarev Date: Thu, 5 May 2016 17:42:29 +0600 Subject: =?UTF-8?q?=D0=A1onvert=20map=20tests=20to=20mocha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mocha_test/map.js | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test-async.js | 282 -------------------------------------------------- 2 files changed, 295 insertions(+), 282 deletions(-) create mode 100644 mocha_test/map.js diff --git a/mocha_test/map.js b/mocha_test/map.js new file mode 100644 index 0000000..c830b21 --- /dev/null +++ b/mocha_test/map.js @@ -0,0 +1,295 @@ +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(); + }); + }); +}); 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){ -- cgit v1.2.1 From d9a03388149e2dbfb23a3095910a707a836ca5d2 Mon Sep 17 00:00:00 2001 From: ezubarev Date: Thu, 5 May 2016 17:59:31 +0600 Subject: Add test case on resolved issue #1106 --- mocha_test/map.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mocha_test/map.js b/mocha_test/map.js index c830b21..5161765 100644 --- a/mocha_test/map.js +++ b/mocha_test/map.js @@ -292,4 +292,20 @@ describe("map", function() { 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(); + }); + }); }); -- cgit v1.2.1