Skip to content

Commit

Permalink
Support for iterators; deprecating generator support
Browse files Browse the repository at this point in the history
  • Loading branch information
timdp committed Sep 27, 2015
1 parent 4191bb2 commit 349167c
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 41 deletions.
21 changes: 14 additions & 7 deletions README.md
Expand Up @@ -6,7 +6,7 @@ Runs `Promise`s in a pool that limits their maximum concurrency.

## Motivation

An ECMAScript 6 `Promise` is a great way of handling asynchronous operations.
An ECMAScript 2015 `Promise` is a great way to handle asynchronous operations.
The `Promise.all` function provides an easy interface to let a bunch of promises
settle concurrently.

Expand All @@ -20,8 +20,8 @@ waiting for any number of promises to settle, while imposing an upper bound on
the number of simultaneously executing promises.

The promises can be created in a just-in-time fashion. You essentially pass a
function that produces a new promise every time it is called. On modern
platforms, you can also use ES6 generator functions for this.
function that produces a new promise every time it is called. Alternatively, you
can pass an ES2015 iterator, meaning you can also use generator functions.

## Compatibility

Expand Down Expand Up @@ -117,23 +117,30 @@ pool.start()
})
```

### Generator
### Iterator

We can achieve the same result with ECMAScript 6 generator functions.
We can achieve the same result with ECMAScript 2015 iterators. Since ES2015
generator functions return such an iterator, we can make the example above a lot
prettier:

```js
const promiseProducer = function * () {
const generatePromises = function * () {
for (let count = 1; count <= 5; count++) {
yield delayValue(count, 1000)
}
}

const pool = new PromisePool(promiseProducer, 3)
const promiseIterator = generatePromises()
const pool = new PromisePool(promiseIterator, 3)

pool.start()
.then(() => console.log('Complete'))
```

It's also possible to pass a generator function directly. In that case, it will
be invoked with no arguments and the resulting iterator will be used. This
feature will however be removed in version 3.

## Events

We can also ask the promise pool to notify us when an individual promise is
Expand Down
19 changes: 19 additions & 0 deletions demo/demo-iterator.js
@@ -0,0 +1,19 @@
(function (global) {
'use strict'

var producer = function (getPromise) {
var i = 0
return {
next: function () {
return (++i < 10) ? {value: getPromise()} : {done: true}
}
}
}

if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = producer
} else {
global._producers = global._producers || {}
global._producers['iterator'] = producer
}
})(this)
1 change: 1 addition & 0 deletions demo/demo.html
Expand Up @@ -21,6 +21,7 @@
<script src="../es6-promise-pool.js"></script>
<script src="log-helper.js"></script>
<script src="demo-function.js"></script>
<script src="demo-iterator.js"></script>
<script src="demo-generator.js"></script>
<script src="demo.js"></script>
</body>
Expand Down
2 changes: 1 addition & 1 deletion demo/demo.js
Expand Up @@ -83,7 +83,7 @@
}
}

var demos = ['function']
var demos = ['function', 'iterator']
if (supportsGenerators()) {
demos.push('generator')
} else {
Expand Down
66 changes: 37 additions & 29 deletions es6-promise-pool.js
Expand Up @@ -42,38 +42,47 @@
}
}

var generatorFunctionToProducer = function (gen) {
gen = gen()
return function () {
var res = gen.next()
return res.done ? null : res.value
var isGenerator = function (func) {
return (typeof func.constructor === 'function' &&
func.constructor.name === 'GeneratorFunction')
}

var functionToIterator = function (func) {
return {
next: function () {
var promise = func()
return promise ? {value: promise} : {done: true}
}
}
}

var promiseToProducer = function (promise) {
var promiseToIterator = function (promise) {
var called = false
return function () {
if (called) {
return null
return {
next: function () {
if (called) {
return {done: true}
}
called = true
return {value: promise}
}
called = true
return promise
}
}

var toProducer = function (obj, Promise) {
var toIterator = function (obj, Promise) {
var type = typeof obj
if (type === 'function') {
if (obj.constructor && obj.constructor.name === 'GeneratorFunction') {
return generatorFunctionToProducer(obj)
} else {
if (type === 'object') {
if (typeof obj.next === 'function') {
return obj
}
if (typeof obj.then === 'function') {
return promiseToIterator(obj)
}
}
if (type !== 'object' || typeof obj.then !== 'function') {
obj = Promise.resolve(obj)
if (type === 'function') {
return isGenerator(obj) ? obj() : functionToIterator(obj)
}
return promiseToProducer(obj)
return promiseToIterator(Promise.resolve(obj))
}

var PromisePoolEvent = function (target, type, data) {
Expand All @@ -92,8 +101,8 @@
this._concurrency = concurrency
this._options = options || {}
this._options.promise = this._options.promise || Promise
this._producer = toProducer(source, this._options.promise)
this._producerDone = false
this._iterator = toIterator(source, this._options.promise)
this._done = false
this._size = 0
this._promise = null
this._callbacks = null
Expand Down Expand Up @@ -185,17 +194,16 @@
}

PromisePool.prototype._proceed = function () {
if (!this._producerDone) {
var promise
while (this._size < this._concurrency && (promise = this._producer())) {
if (!this._done) {
var result = null
while (this._size < this._concurrency &&
!(result = this._iterator.next()).done) {
this._size++
this._trackPromise(promise)
}
if (!promise) {
this._producerDone = true
this._trackPromise(result.value)
}
this._done = (result === null || !!result.done)
}
if (this._producerDone && this._size === 0) {
if (this._done && this._size === 0) {
this._settle()
}
}
Expand Down
21 changes: 17 additions & 4 deletions test/test.js
Expand Up @@ -99,12 +99,25 @@
return expect(poolPromise).to.be.fulfilled
})

it('accepts an iterator as the producer', function () {
var called = false
var iterator = {
next: function () {
if (called) {
return {done: true}
}
called = true
return {value: Promise.resolve()}
}
}
var poolPromise = new PromisePool(iterator, 1).start()
return expect(poolPromise).to.be.fulfilled
})

if (supportsGenerators) {
var gen = eval('' + // eslint-disable-line no-eval
'(function* g() {' +
'yield new Promise(function(resolve, reject) {' +
'resolve();' +
'});' +
'(function * g() {' +
' yield Promise.resolve()' +
'})')
it('accepts a generator as the producer', function () {
var poolPromise = new PromisePool(gen, 1).start()
Expand Down

0 comments on commit 349167c

Please sign in to comment.