lua-promises icon indicating copy to clipboard operation
lua-promises copied to clipboard

Runtime errors are not reported in chained promises

Open joagre opened this issue 8 years ago • 2 comments

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 avatar Nov 02 '16 05:11 joagre

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

joagre avatar Nov 15 '16 19:11 joagre

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)

zserge avatar Jan 01 '17 15:01 zserge