diff --git a/lib/README.md b/lib/README.md index e540d16..b2ac9f1 100644 --- a/lib/README.md +++ b/lib/README.md @@ -359,15 +359,27 @@ docs: - name: fold since: unreleased 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. + ```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: - name: list - type: "array" + type: "array>" - name: reducer desc: The function to call with the accumulated value, the current element from the array and its index. type: @@ -376,7 +388,7 @@ docs: returns: U | Promise - name: initialValue type: "U" - returns: U | Promise + returns: Promise static: true - name: each diff --git a/lib/init.lua b/lib/init.lua index 3b9c87b..e5a77ad 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -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(callback) == "function", "Bad argument #2 to Promise.fold: must be a function") - local previousValue = initialValue - for i = 1, #list do - local element = list[i] - if Promise.is(previousValue) then - previousValue = previousValue:andThen(function(previousValueResolved) - return callback(previousValueResolved, element, i) + local accumulator = initialValue + return Promise.each(list, function(resolvedElement, i) + if Promise.is(accumulator) then + accumulator = accumulator:andThen(function(previousValueResolved) + return callback(previousValueResolved, resolvedElement, i) end) else - previousValue = callback(previousValue, element, i) + accumulator = callback(accumulator, resolvedElement, i) end - end - return previousValue + end):andThenReturn(accumulator) end function Promise.some(promises, amount) diff --git a/lib/init.spec.lua b/lib/init.spec.lua index f4ea3bf..1bfc996 100644 --- a/lib/init.spec.lua +++ b/lib/init.spec.lua @@ -812,30 +812,37 @@ return function() end) 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 result = Promise.fold({}, function() error("should not be called") 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) - it("should fold the list if the reducer never returns promises", function() - local sum = Promise.fold({1, 2, 3}, function(sum, element) + it("should accept promises in the list", function() + local sum = Promise.fold({Promise.resolve(1), 2, 3}, function(sum, element) return sum + element 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) - 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) if index == 2 then - return Promise.resolve(sum + element) + return Promise.delay(1):andThenReturn(sum + element) else return sum + element end end, 0) 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:expect()).to.equal(6) end) @@ -854,6 +861,24 @@ return function() expect(status).to.equal(Promise.Status.Rejected) expect(rejection).to.equal(errorMessage) 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) describe("Promise.race", function()