zserge / lua-promises

A+ promises in Lua
https://zserge.com/lua-promises/
MIT License
220 stars 35 forks source link

Runtime errors are not reported in chained promises #4

Open joagre opened 7 years ago

joagre commented 7 years ago

If I make a programming error in the first promise (in a promise chain) I get an expected runtime error but if I make a programming error in the second promise I get no runtime error.

I slightly modifed your chained promises example to verify this:

local deferred = require "lib.deferred"

function readasync(filename, cb)
  if filename == 'first.txt' then
    call_missing_function()
    cb("content1", nil)
  else
    --call_missing_function()
    cb("content2", nil)
  end
end

function read(filename)
  local d = deferred.new()
  readasync(filename, function(contents, err)
    if err == nil then
      d:resolve(contents)
    else
      d:reject(err)
    end
  end)
  return d
end

read('first.txt'):next(function(s)
  print('First file:', s)
  return read('second.txt')
end):next(function(s)
  print('Second file:', s)
end):next(nil, function(err)
  -- error while reading first or second file
  print('Error:', err)
end)

When I run the above I get an expected runtime error:

nov. 02 06:38:45.241 ERROR: Runtime error
  /Users/jocke/src/blackmode/trunk/app/ui2/main.lua:5: attempt to call global 'call_missing_function' (a nil value)
  stack traceback:
  /Users/jocke/src/blackmode/trunk/app/ui2/main.lua:5: in function 'readasync'
  /Users/jocke/src/blackmode/trunk/app/ui2/main.lua:15: in function 'read'
  /Users/jocke/src/blackmode/trunk/app/ui2/main.lua:25: in main chunk

If I change the readsync function above to this:

function readasync(filename, cb)
  if filename == 'first.txt' then
    --call_missing_function()
    cb("content1", nil)
  else
    call_missing_function()
    cb("content2", nil)
  end
end

Then I just get the custom error message and no run-time error:

Error:  /Users/jocke/src/blackmode/trunk/app/ui2/main.lua:8: attempt to call global 'call_missing_function' (a nil value)

I this to be expected?

Kind regards

joagre commented 7 years ago

I ended up removing all the pcalls i deferred.lua. Works like a charm.

zserge commented 7 years ago

Sorry for a late reply.

You're right, this may be confusing. According to the A+ spec:

If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.

That's why throwing no runtime errors and passing them to error callback is the expected behavior. The catch is that you first function is called like a normal function and thus there is no outer handler to catch the runtime error.

I've just pushed a tiny change to the library. Now deferred.new() may (and should) receive a function that deals with the deferred object. There is no need to create deferred object manually anymore. With this modification your example should be rewritten like this:

local deferred = require "deferred"

function readasync(filename, cb)
  if filename == 'first.txt' then
    call_missing_function()
    cb("content1", nil)
  else
    --call_missing_function()
    cb("content2", nil)
  end
end

function read(filename)
    return function(d)
        readasync(filename, function(contents, err)
            if err == nil then
                d:resolve(contents)
            else
                d:reject(err)
            end
        end)
    end
end

deferred.new(read('first.txt')):next(function(s)
  print('First file:', s)
  return read('second.txt')
end):next(function(s)
  print('Second file:', s)
end):next(nil, function(err)
  -- error while reading first or second file
  print('Error:', err)
end)