Skip to content

Commit

Permalink
Require arrays as input for promise-array related functions (closes #35)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsor committed May 2, 2016
1 parent d22b960 commit e8c20be
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 462 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ CHANGELOG
* Introduce a global task queue to eliminate recursion and reduce stack size
when chaining promises.
* BC break: The progression API has been removed (#32).
* BC break: The promise-array related functions (`all()`, `race()`, `any()`,
`some()`, `map()`, `reduce()`) now require an array of promises or values
as input. Before, arrays and promises which resolve to an array were
supported, other input types resolved to empty arrays or `null`. (#35).

* 2.4.0 (2016-03-31)

Expand Down
233 changes: 105 additions & 128 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,191 +34,168 @@ function reject($promiseOrValue = null)
return new RejectedPromise($promiseOrValue);
}

function all($promisesOrValues)
function all(array $promisesOrValues)
{
return map($promisesOrValues, function ($val) {
return $val;
});
}

function race($promisesOrValues)
function race(array $promisesOrValues)
{
if (!$promisesOrValues) {
return resolve();

This comment has been minimized.

Copy link
@joshdifabio

joshdifabio May 4, 2016

Contributor

Does this mean race will accept an empty array? Is this intentional?

This comment has been minimized.

Copy link
@jsor

jsor May 4, 2016

Author Member

No, this is a bug. I just kept the current (2.x) behavior. Following Promise.race from ES6, this must return a forever pending promise and should be documented. Care to file an issue?

}

$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);

return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($cancellationQueue, $resolve, $reject) {
if (!is_array($array) || !$array) {
$resolve();
return;
}
$fulfiller = function ($value) use ($cancellationQueue, $resolve) {
$cancellationQueue();
$resolve($value);
};

$fulfiller = function ($value) use ($cancellationQueue, $resolve) {
$cancellationQueue();
$resolve($value);
};
$rejecter = function ($reason) use ($cancellationQueue, $reject) {
$cancellationQueue();
$reject($reason);
};

$rejecter = function ($reason) use ($cancellationQueue, $reject) {
$cancellationQueue();
$reject($reason);
};
foreach ($promisesOrValues as $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);

foreach ($array as $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);

resolve($promiseOrValue)
->done($fulfiller, $rejecter);
}
}, $reject);
resolve($promiseOrValue)
->done($fulfiller, $rejecter);
}
}, $cancellationQueue);
}

function any($promisesOrValues)
function any(array $promisesOrValues)
{
return some($promisesOrValues, 1)
->then(function ($val) {
return array_shift($val);
});
}

function some($promisesOrValues, $howMany)
function some(array $promisesOrValues, $howMany)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
if ($howMany < 1) {
return resolve([]);
}

return new Promise(function ($resolve, $reject) use ($promisesOrValues, $howMany, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject) {
if (!is_array($array) || $howMany < 1) {
$resolve([]);
return;
}
$len = count($promisesOrValues);

if ($len < $howMany) {
return reject(
new Exception\LengthException(
sprintf(
'Input array must contain at least %d item%s but contains only %s item%s.',
$howMany,
1 === $howMany ? '' : 's',
$len,
1 === $len ? '' : 's'
)
)
);
}

$len = count($array);

if ($len < $howMany) {
throw new Exception\LengthException(
sprintf(
'Input array must contain at least %d item%s but contains only %s item%s.',
$howMany,
1 === $howMany ? '' : 's',
$len,
1 === $len ? '' : 's'
)
);
}
$cancellationQueue = new CancellationQueue();

$toResolve = $howMany;
$toReject = ($len - $toResolve) + 1;
$values = [];
$reasons = [];
return new Promise(function ($resolve, $reject) use ($len, $promisesOrValues, $howMany, $cancellationQueue) {
$toResolve = $howMany;
$toReject = ($len - $toResolve) + 1;
$values = [];
$reasons = [];

foreach ($array as $i => $promiseOrValue) {
$fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve, $cancellationQueue) {
if ($toResolve < 1 || $toReject < 1) {
return;
}
foreach ($promisesOrValues as $i => $promiseOrValue) {
$fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve, $cancellationQueue) {
if ($toResolve < 1 || $toReject < 1) {
return;
}

$values[$i] = $val;
$values[$i] = $val;

if (0 === --$toResolve) {
$cancellationQueue();
$resolve($values);
}
};
if (0 === --$toResolve) {
$cancellationQueue();
$resolve($values);
}
};

$rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
if ($toResolve < 1 || $toReject < 1) {
return;
}
$rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
if ($toResolve < 1 || $toReject < 1) {
return;
}

$reasons[$i] = $reason;
$reasons[$i] = $reason;

if (0 === --$toReject) {
$reject($reasons);
}
};
if (0 === --$toReject) {
$reject($reasons);
}
};

$cancellationQueue->enqueue($promiseOrValue);
$cancellationQueue->enqueue($promiseOrValue);

resolve($promiseOrValue)
->done($fulfiller, $rejecter);
}
}, $reject);
resolve($promiseOrValue)
->done($fulfiller, $rejecter);
}
}, $cancellationQueue);
}

function map($promisesOrValues, callable $mapFunc)
function map(array $promisesOrValues, callable $mapFunc)
{
if (!$promisesOrValues) {
return resolve([]);
}

$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);

return new Promise(function ($resolve, $reject) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject) {
if (!is_array($array) || !$array) {
$resolve([]);
return;
}

$toResolve = count($array);
$values = [];
$toResolve = count($promisesOrValues);
$values = [];

foreach ($array as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
foreach ($promisesOrValues as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);

resolve($promiseOrValue)
->then($mapFunc)
->done(
function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
$values[$i] = $mapped;
resolve($promiseOrValue)
->then($mapFunc)
->done(
function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
$values[$i] = $mapped;

if (0 === --$toResolve) {
$resolve($values);
}
},
$reject
);
}
}, $reject);
if (0 === --$toResolve) {
$resolve($values);
}
},
$reject
);
}
}, $cancellationQueue);
}

function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
function reduce(array $promisesOrValues, callable $reduceFunc, $initialValue = null)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);

return new Promise(function ($resolve, $reject) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject) {
if (!is_array($array)) {
$array = [];
}

$total = count($array);
$i = 0;
$total = count($promisesOrValues);
$i = 0;

// Wrap the supplied $reduceFunc with one that handles promises and then
// delegates to the supplied.
$wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
$cancellationQueue->enqueue($val);
$wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
$cancellationQueue->enqueue($val);

return $current
->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
return resolve($val)
->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
return $reduceFunc($c, $value, $i++, $total);
});
return $current
->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
return resolve($val)
->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
return $reduceFunc($c, $value, $i++, $total);
});
};
});
};

$cancellationQueue->enqueue($initialValue);
$cancellationQueue->enqueue($initialValue);

array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
->done($resolve, $reject);
}, $reject);
array_reduce($promisesOrValues, $wrappedReduceFunc, resolve($initialValue))
->done($resolve, $reject);
}, $cancellationQueue);
}

Expand Down
26 changes: 0 additions & 26 deletions tests/FunctionAllTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,4 @@ public function shouldRejectIfAnyInputPromiseRejects()
all([resolve(1), reject(2), resolve(3)])
->then($this->expectCallableNever(), $mock);
}

/** @test */
public function shouldAcceptAPromiseForAnArray()
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo([1, 2, 3]));

all(resolve([1, 2, 3]))
->then($mock);
}

/** @test */
public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo([]));

all(resolve(1))
->then($mock);
}
}
Loading

0 comments on commit e8c20be

Please sign in to comment.