refactor implementation to support cancel

This commit is contained in:
otrepanier 2020-11-30 16:34:13 -08:00
parent df07638f43
commit 1ac7d3cae9
3 changed files with 55 additions and 20 deletions

View file

@ -359,15 +359,27 @@ docs:
- name: fold - name: fold
since: unreleased since: unreleased
desc: | desc: |
Folds an array into a promise or a value. The array is traversed sequentially and Folds an array of values or promises into a single value. The array is traversed sequentially.
The reducer function can return a promise or directly a value. It always received the last resolved value. The reducer function can return a promise or value directly. Each iteration receives the resolved value from the previous, and the first receives your defined initial value.
The folding will stop at the first rejection encountered. The folding will stop at the first rejection encountered.
```lua
local basket = {"blueberry", "melon", "pear", "melon"}
Promise.fold(basket, function(accumulatedCost, fruit)
if fruit == "blueberry" then
return cost -- blueberries are free!
else
-- call a function that returns a promise with the fruit price
return fetchPrice(fruit):andThen(function(fruitCost)
return cost + fruitCost
end)
end
end, 0)
``` ```
params: params:
- name: list - name: list
type: "array<T>" type: "array<T | Promise<T>>"
- name: reducer - name: reducer
desc: The function to call with the accumulated value, the current element from the array and its index. desc: The function to call with the accumulated value, the current element from the array and its index.
type: type:
@ -376,7 +388,7 @@ docs:
returns: U | Promise<U> returns: U | Promise<U>
- name: initialValue - name: initialValue
type: "U" type: "U"
returns: U | Promise<U> returns: Promise<U>
static: true static: true
- name: each - name: each

View file

@ -443,18 +443,16 @@ function Promise.fold(list, callback, initialValue)
assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table") assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table")
assert(type(callback) == "function", "Bad argument #2 to Promise.fold: must be a function") assert(type(callback) == "function", "Bad argument #2 to Promise.fold: must be a function")
local previousValue = initialValue local accumulator = initialValue
for i = 1, #list do return Promise.each(list, function(resolvedElement, i)
local element = list[i] if Promise.is(accumulator) then
if Promise.is(previousValue) then accumulator = accumulator:andThen(function(previousValueResolved)
previousValue = previousValue:andThen(function(previousValueResolved) return callback(previousValueResolved, resolvedElement, i)
return callback(previousValueResolved, element, i)
end) end)
else else
previousValue = callback(previousValue, element, i) accumulator = callback(accumulator, resolvedElement, i)
end end
end end):andThenReturn(accumulator)
return previousValue
end end
function Promise.some(promises, amount) function Promise.some(promises, amount)

View file

@ -812,30 +812,37 @@ return function()
end) end)
describe("Promise.fold", function() describe("Promise.fold", function()
it("should return the initial value when the list is empty", function() it("should return the initial value in a promise when the list is empty", function()
local initialValue = {} local initialValue = {}
local result = Promise.fold({}, function() local result = Promise.fold({}, function()
error("should not be called") error("should not be called")
end, initialValue) end, initialValue)
expect(result).to.equal(initialValue)
expect(Promise.is(result)).to.equal(true)
expect(result:getStatus()).to.equal(Promise.Status.Resolved)
expect(result:expect()).to.equal(initialValue)
end) end)
it("should fold the list if the reducer never returns promises", function() it("should accept promises in the list", function()
local sum = Promise.fold({1, 2, 3}, function(sum, element) local sum = Promise.fold({Promise.resolve(1), 2, 3}, function(sum, element)
return sum + element return sum + element
end, 0) end, 0)
expect(sum).to.equal(6) expect(Promise.is(sum)).to.equal(true)
expect(sum:getStatus()).to.equal(Promise.Status.Resolved)
expect(sum:expect()).to.equal(6)
end) end)
it("should fold the list into a promise if the reducer returns at least a promise", function() it("should always return a promise even if the list or reducer don't use them", function()
local sum = Promise.fold({1, 2, 3}, function(sum, element, index) local sum = Promise.fold({1, 2, 3}, function(sum, element, index)
if index == 2 then if index == 2 then
return Promise.resolve(sum + element) return Promise.delay(1):andThenReturn(sum + element)
else else
return sum + element return sum + element
end end
end, 0) end, 0)
expect(Promise.is(sum)).to.equal(true) expect(Promise.is(sum)).to.equal(true)
expect(sum:getStatus()).to.equal(Promise.Status.Started)
advanceTime(2)
expect(sum:getStatus()).to.equal(Promise.Status.Resolved) expect(sum:getStatus()).to.equal(Promise.Status.Resolved)
expect(sum:expect()).to.equal(6) expect(sum:expect()).to.equal(6)
end) end)
@ -854,6 +861,24 @@ return function()
expect(status).to.equal(Promise.Status.Rejected) expect(status).to.equal(Promise.Status.Rejected)
expect(rejection).to.equal(errorMessage) expect(rejection).to.equal(errorMessage)
end) end)
it("should return the first canceled promise", function()
local secondPromise
local sum = Promise.fold({1, 2, 3}, function(sum, element, index)
if index == 1 then
return sum + element
elseif index == 2 then
secondPromise = Promise.delay(1):andThenReturn(sum + element)
return secondPromise
else
error('this should not run if the promise is cancelled')
end
end, 0)
expect(Promise.is(sum)).to.equal(true)
expect(sum:getStatus()).to.equal(Promise.Status.Started)
secondPromise:cancel()
expect(sum:getStatus()).to.equal(Promise.Status.Cancelled)
end)
end) end)
describe("Promise.race", function() describe("Promise.race", function()