Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support Iterable objects, such as Map, Set, String #415

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

KHYehor
Copy link

@KHYehor KHYehor commented Mar 17, 2019

I added support for iterable objects using [Symbol.iterator],
also tests are written

@KHYehor KHYehor requested a review from belochub March 17, 2019 20:07
@nechaido nechaido requested a review from o-rumiantsev March 18, 2019 07:59
@belochub belochub requested review from nechaido and mille-nium and removed request for o-rumiantsev March 18, 2019 07:59
@nechaido nechaido requested a review from o-rumiantsev March 18, 2019 08:01
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
@o-rumiantsev
Copy link
Member

It is better to use common.iter to iterate over the iterable.

lib/array.js Outdated
if (done) result[count] = itemResult;
const itemResult = fn(data.next().value, count);
if (done) {
if (name === 'Array') result.push(itemResult);
Copy link
Member

@mille-nium mille-nium Mar 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, an object with these types as keys and an appropriate mapping functions as values looks prettier for purpose like this. I suppose, that this piece of code will not be relevant after using common.iter, however just pointing it out for future reference.

Copy link
Author

@KHYehor KHYehor Mar 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask you a question?
If I replace my const data = items[Symbol.iterator]();
with const data = common.iter(items); I will get almost the same, and I have no need to rewrite code where I use data.next().value.
So, Is it what I am expected to do?

Copy link
Member

@mille-nium mille-nium Mar 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've missconcepted this, you may need here to use a constructor name to add element to result. So it still make sense, AFAIS, to move this conditional statement to a separate function or to make an mapping object described previously, because it is used at least four times in this file, kind of boilerplate code, if I'm not mistaken.

Copy link
Author

@KHYehor KHYehor Mar 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mille-nium What do you think about this solution with separate function?

const push = (name, result, value) => {
  switch (name) {
    case 'Set':
      return result.add(value);
    case 'Map':
      return result.set(value[0], value[1]);
    case 'Array':
      return result.push(value);
    case 'String':
      return result += value;
  }
  return null;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer not using switch-case here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask you again?
If I separate push function prettier code says that I should change my let result to const result How can I solute this? Can I turn off this option, or what?

Copy link
Author

@KHYehor KHYehor Mar 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, If I give string into object pushValue, I won't concat my string with string += value
If I do something like this, It will work

      return;
    }
    if (name === 'String') result += value;
    else push(name, result, value);
    count++;
    if (count === len) done(null, result);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, if you could simply use common.Iterator.map() and others methods and then just common.Iterator.collectTo()?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be, I will check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose, this was actually meant by @o-rumiantsev

lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
@@ -60,7 +60,7 @@ metatests.test('every with two-element arrays', test =>
)
);

metatests.test('every', test => {
metatests.test('every / array', test => {
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be moved to a separate variable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly should I move to separate variable?
const data = [...]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]. I don't mind different name though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still relevant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping

test/array.find.js Outdated Show resolved Hide resolved
@o-rumiantsev
Copy link
Member

o-rumiantsev commented Mar 19, 2019

It is better to use common.iter to iterate over the iterable.

You can also use metasync.asyncIter instead of common.iter where needed

@KHYehor
Copy link
Author

KHYehor commented Mar 20, 2019

@o-rumiantsev I have one misunderstanding with you. It is said that
Asynchronous map (iterate parallel) and there isn't used any async/await or promises, only call back next(). If I insert common.iter I will get the same that I had. If I insert metasync.iter it will be some different, and I also get strange error messages Message: Failure after end: callback is not a function. Or I just can't understand something.

@KHYehor
Copy link
Author

KHYehor commented Mar 24, 2019

Now I have one inaccuracy. If I call method .fetch() then call other methods like .filter(), .reduce(), ... I will get wrong behavior. Because string this.execute() is async, and I return this which isn't calculated yet. It could be solved with Promise or async/await but you might solute something else.
test/chain.js 'for chain after fetch' I am talking about this one.

ArrayChain.prototype.fetch = function(fn) {
  this.chain.push({ op: 'fetch', fn });
  this.execute();
  return this;
};

@KHYehor
Copy link
Author

KHYehor commented Mar 25, 2019

@SemenchenkoVitaliy @belochub I noticed one interesting detail, I can be wrong so correct me if I am.
If I rewrite metatest 'for chain after fetch' to process.nextTick like this

metatests.test('for chain after fetch', test => {
  metasync
    .for([1, 2, 3, 4])
    .map((item, cb) => process.nextTick(() => cb(null, item * item)))
    .filter((item, cb) => process.nextTick(() => cb(null, item > 5)))
    .fetch((error, result, resume) => {
      test.error(error);
      test.strictSame(result, [9, 16]);
      process.nextTick(() => resume(null, result));
    })
    .filter((item, cb) => {
      process.nextTick(() => cb(null, item > 10));
    })
    .map((item, cb) => {
      process.nextTick(() => cb(null, --item));
    })
    .fetch((error, result) => {
      test.error(error);
      test.strictSame(result, [15]);
      test.end();
    });
});

I will get async functions, but it doesn't work as should, I will get errors. (it is master)
And If I rewrite all methods to asyncIter as I made, I will get the same errors with any fn.
How should it be solute?

@KHYehor
Copy link
Author

KHYehor commented Mar 29, 2019

In last commit I have described my problem with lib/chain.js I am trying to find the solution for this. And you can check and say what you think about lib/array.js.
P.S. Tests failed because of reserved words await and next() in v6.x and v8.x

lib/array.js Outdated Show resolved Hide resolved
lib/chain.js Outdated Show resolved Hide resolved
@@ -60,7 +60,7 @@ metatests.test('every with two-element arrays', test =>
)
);

metatests.test('every', test => {
metatests.test('every / array', test => {
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still relevant.

lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated
const promise =
isArray || items.constructor.name === 'String'
? data.toArray()
: data.collectTo(items.constructor);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After consulting with @belochub, we agreed that we should return an Iterator object if items is not an Array.

lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/control.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
@KHYehor KHYehor force-pushed the support-iterable branch from eabbd1d to 1070a5f Compare April 5, 2019 14:11
Copy link
Member

@nechaido nechaido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Majority of comments apply to every function.

lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated
@@ -231,16 +191,17 @@ const REDUCE_RIGHT_EMPTY_ARR =
// argument in first iteration
const reduceRight = (items, fn, done, initial) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduceRight should probably be implemented on AsyncIterator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping

lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/control.js Outdated Show resolved Hide resolved
lib/array.js Outdated Show resolved Hide resolved
lib/array.js Outdated
@@ -231,16 +191,17 @@ const REDUCE_RIGHT_EMPTY_ARR =
// argument in first iteration
const reduceRight = (items, fn, done, initial) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping

test/array.find.js Outdated Show resolved Hide resolved
);
});

// metatests.test('each with error', test => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is commented out?

Copy link
Author

@KHYehor KHYehor Apr 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because now each works parallel and this callback callback(eachError); will not cause the stopping of processing other items.
What should I do with this test? rewrite for checking if other elements were processed?

test/chain.js Outdated
test.end();
});
// .filter((item, cb) => process.nextTick(() => cb(null, item < 4)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem with fetch after fetch wasn't solved by me. Ask @nechaido.

lib/array.js Outdated
.parallel(item => promisify(fn)(item))
.then(res => {
const accepted = res.every(predicateResult => predicateResult);
done(null, accepted);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
done(null, accepted);
done(null, res.every(e => e);

@@ -213,6 +221,16 @@ class MapIterator extends AsyncIterator {
}
}

class ReverseIterator extends AsyncIterator {
constructor(base) {
const newBase = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done on the first next invocation. base is always AsyncInterotor.

@o-rumiantsev o-rumiantsev requested a review from nechaido May 24, 2019 05:36
lib/array.js Outdated
}
const isArray = Array.isArray(items);
asyncIter(items)
.parallel(item => promisify(fn)(item))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.parallel(item => promisify(fn)(item))
.parallel(promisify(fn))

lib/array.js Outdated
next();
const isArray = Array.isArray(items);
const iter = asyncIter(items)
.map(item => promisify(fn)(item))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map(item => promisify(fn)(item))
.map(promisify(fn))

});

metatests.test('with array without element which is searching', test => {
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
metatests.test('find without element which is searching', test => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metatests.test('find without element which is searching', test => {
metatests.test('find without the desired element', test => {

});

metatests.test('reduce without initial and with single-element array', test => {
metatests.test('reduce single-element array without initial', test => {
const arr = [2];

metasync.reduce(
arr,
(prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)),
test.mustNotCall(),

lib/array.js Outdated
done(null, []);
// result - <Iterable>
const map = (items, fn, done = common.emptiness) => {
if (!items[Symbol.iterator]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, Iterable can also contain Symbol.asyncIterator and do not contain Symbol.iterator.

const asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return Promise.resolve({ value: this.i++, done: false });
        }

        return Promise.resolve({ done: true });
      }
    };
  }
};

(async function() {
   for await (const i of asyncIterable) {
     console.log(i);
   }
})();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU we can omit this check because AsyncIterator constructor will make them.

lib/array.js Outdated

if (!items.length) {
if (done) done(null, []);
if (!items[Symbol.iterator]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

lib/array.js Outdated
if (!len) {
done(null, []);
const filter = (items, fn, done = common.emptiness) => {
if (!items[Symbol.iterator]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

lib/array.js Show resolved Hide resolved
lib/array.js Show resolved Hide resolved
lib/array.js Show resolved Hide resolved
lib/array.js Show resolved Hide resolved
lib/array.js Show resolved Hide resolved
@nechaido nechaido requested a review from ivan-tymoshenko May 27, 2019 12:55
@nechaido nechaido removed the request for review from o-rumiantsev May 27, 2019 12:56
@nechaido nechaido closed this Jun 10, 2019
@nechaido nechaido reopened this Jun 18, 2019
@o-rumiantsev o-rumiantsev requested a review from nechaido June 20, 2019 09:33
@nechaido
Copy link
Member

ping @belochub.

Copy link
Member

@nechaido nechaido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, split this PR into two commits: one that adds support for Iterables and the one that "updates" tests.

lib/array.js Outdated
.parallel(async item => [await promisify(fn)(item), item])
.then(res => {
const filtered = res
.filter(([predicateResult]) => predicateResult)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common.Iterator.filterMap ?

const arr = [1, 2, 3];
const expectedArr = [2, 4, 6];

metasync.asyncMap(
arr,
item => item * 2,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to leave this test the way it is, to ensure that synchronous functions are still supported.

@@ -26,23 +26,23 @@ const fewStrictSameResult = (inOutPairs, test) => {

metatests.test('every with error', test => {
const data = [1, 2, 3];
const everyErr = new Error('Every error');
const everyError = new Error('Every error');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to rename variables.

metatests.test('every with empty array', test =>
strictSameResult([], true, test, () => test.end())
);
metatests.test('every with empty', test => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to change this?

@o-rumiantsev o-rumiantsev force-pushed the support-iterable branch 2 times, most recently from ee7985b to 4f027ba Compare July 1, 2019 15:56
o-rumiantsev added a commit that referenced this pull request Jul 1, 2019
o-rumiantsev added a commit that referenced this pull request Jul 1, 2019
o-rumiantsev added a commit that referenced this pull request Jul 1, 2019
o-rumiantsev added a commit that referenced this pull request Jul 1, 2019
// result - <Iterable>
const map = (items, fn, done = common.emptiness) => {
const isArray = Array.isArray(items);
asyncIter(items)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think wrapping everything in AsyncIterator and additionally using util.promisify() adds unnecessary overhead, that can be avoided by just rewriting the original functions with iterators in mind. This also won't require any changes to metasync.AsyncIterator. If we decide to keep it the way it is in this PR we should probably at least benchmark this variant to make sure this doesn't lead to significant performance regressions.
/cc @tshemsedinov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants