Skip to content

Commit 70b0906

Browse files
committed
fix(promise): improve reason for unhandled rejection
Error in Lua is messy because of the `errfunc` in `lua_pcall`. 1. If reject reason by Promise, only append UnhandledPromiseRejection header; 2. If invoke error() in a function, prefer use the result caught by `pcall` as reason for UnhandledPromiseRejection.
1 parent 005df1a commit 70b0906

File tree

3 files changed

+35
-39
lines changed

3 files changed

+35
-39
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ The environment in `Async.sync` function have been injected some new functions f
122122
enhancement:
123123

124124
1. `await`: A reference of `Async.wait` function;
125-
2. `pcall`: Be compatible with LuaJIT and handle error throws by Promise;
126-
3. `xpcall`: Be compatible with LuaJIT and handle error throws by Promise;
125+
2. `pcall`: Be compatible with LuaJIT;
126+
3. `xpcall`: Be compatible with LuaJIT;
127127

128128
`async` in JavaScript return Promise object only with single result, but may carry multiple results
129129
in Lua. The resolved result of Promise object return by `async` function will be packed into a table

lua/async.lua

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
local promise = require('promise')
22
local utils = require('promise-async.utils')
33
local compat = require('promise-async.compat')
4-
local errFactory = require('promise-async.error')
5-
local shortSrc = debug.getinfo(1, 'S').short_src
64

75
local asyncId = {'promise-async'}
86

@@ -33,42 +31,22 @@ local function apcall(f, ...)
3331
return true, ...
3432
end
3533
local err = select(1, ...)
36-
return false, errFactory.isInstance(err) and err:peek() or err
34+
return false, err
3735
end
3836

3937
return result(compat.pcall(f, ...))
4038
end
4139

42-
local function axpcall(f, msgh, ...)
43-
return compat.xpcall(f, function(err)
44-
return msgh(errFactory.isInstance(err) and err:peek() or err)
45-
end, ...)
46-
end
47-
4840
local function injectENV(fn)
4941
compat.setfenv(fn, setmetatable({
5042
await = Async.wait,
5143
pcall = apcall,
52-
xpcall = axpcall
44+
xpcall = compat.xpcall
5345
}, {
5446
__index = compat.getfenv(fn)
5547
}))
5648
end
5749

58-
local function buildError(thread, level, err)
59-
if not errFactory.isInstance(err) then
60-
err = errFactory.new(err)
61-
level = level + 1
62-
end
63-
local ok, value
64-
repeat
65-
ok, value = errFactory.format(thread, level, shortSrc)
66-
level = level + 1
67-
err:push(value)
68-
until not ok
69-
return err
70-
end
71-
7250
---Export wait function to someone needs
7351
---@param executor fun()
7452
---@return Promise
@@ -117,15 +95,15 @@ function Async.sync(executor)
11795
end)
11896
end
11997

120-
---Export wait function to someone needs, wait function actually have been injected as `async`
98+
---Export wait function to someone needs, wait function actually have been injected as `await`
12199
---into the executor of async function
122100
---@param p Promise|table
123101
---@return ...
124102
function Async.wait(p)
125103
p = promise.resolve(p)
126104
local err, res = coroutine.yield(p)
127105
if err then
128-
error(buildError(coroutine.running(), 2, res))
106+
error(res, 0)
129107
elseif hasPacked(res) then
130108
return compat.unpack(res)
131109
else

lua/promise.lua

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
local utils = require('promise-async.utils')
22

33
local promiseId = {'promise-async'}
4+
local errFactory = require('promise-async.error')
5+
local shortSrc = debug.getinfo(1, 'S').short_src
46

57
---@diagnostic disable: undefined-doc-name
68
---@alias PromiseState
@@ -19,6 +21,7 @@ local REJECTED = 3
1921
---@field queue table
2022
---@field loop PromiseAsyncLoop
2123
---@field needHandleRejection? boolean
24+
---@field err? PromiseAsyncError
2225
---@overload fun(executor: PromiseExecutor): Promise
2326
local Promise = setmetatable({_id = promiseId}, {
2427
__call = function(self, executor)
@@ -74,11 +77,11 @@ function Promise.isInstance(o, typ)
7477
return (typ or type(o)) == 'table' and o._id == promiseId
7578
end
7679

77-
---must one time get `thenCall` field from `o`, can't call repeatedly.
80+
---must get `thenCall` field from `o` at one time, can't call repeatedly.
7881
---@param o any
7982
---@param typ? type
8083
---@return function?
81-
function Promise.getThenable(o, typ)
84+
local function getThenable(o, typ)
8285
local thenCall
8386
if (typ or type(o)) == 'table' then
8487
thenCall = o.thenCall
@@ -89,6 +92,20 @@ function Promise.getThenable(o, typ)
8992
return thenCall
9093
end
9194

95+
---@param err any
96+
---@return PromiseAsyncError
97+
local function buildError(err)
98+
local o = errFactory.new(err)
99+
local level = 3
100+
local ok, value
101+
repeat
102+
ok, value = errFactory.format(coroutine.running(), level, shortSrc)
103+
level = level + 1
104+
o:push(value)
105+
until not ok
106+
return o
107+
end
108+
92109
local resolvePromise, rejectPromise
93110

94111
---@param promise Promise
@@ -125,6 +142,7 @@ local function handleQueue(promise)
125142
if ok then
126143
resolvePromise(newPromise, res)
127144
else
145+
newPromise.err = buildError(res)
128146
rejectPromise(newPromise, res)
129147
end
130148
end
@@ -171,6 +189,7 @@ local function wrapExecutor(promise, executor, self)
171189
ok, res = pcall(executor, resolve, reject)
172190
end
173191
if not ok and not called then
192+
promise.err = buildError(res)
174193
reject(res)
175194
end
176195
end
@@ -182,13 +201,12 @@ local function handleRejection(promise)
182201
Promise.loop.nextIdle(function()
183202
if promise.needHandleRejection then
184203
promise.needHandleRejection = nil
185-
local errFactory = require('promise-async.error')
186-
local reason = promise.result
187-
if not errFactory.isInstance(reason) then
188-
reason = errFactory.new(reason)
204+
local err = promise.err
205+
if not err then
206+
err = errFactory.new(promise.result)
189207
end
190-
reason:unshift('UnhandledPromiseRejection with the reason:')
191-
error(reason)
208+
err:unshift('UnhandledPromiseRejection with the reason:')
209+
error(err)
192210
end
193211
end)
194212
end
@@ -217,7 +235,7 @@ resolvePromise = function(promise, value)
217235
rejectPromise(promise, reason)
218236
end)
219237
else
220-
local thenCall = Promise.getThenable(value, valueType)
238+
local thenCall = getThenable(value, valueType)
221239
if thenCall then
222240
wrapExecutor(promise, thenCall, value)
223241
else
@@ -277,7 +295,7 @@ function Promise:finally(onFinally)
277295
return value
278296
end, function(reason)
279297
wrapFinally()
280-
error(reason)
298+
return Promise.reject(reason)
281299
end)
282300
end
283301

@@ -289,7 +307,7 @@ function Promise.resolve(value)
289307
return value
290308
else
291309
local o = Promise.new(noop)
292-
local thenCall = Promise.getThenable(value, typ)
310+
local thenCall = getThenable(value, typ)
293311
if thenCall then
294312
wrapExecutor(o, thenCall, value)
295313
else

0 commit comments

Comments
 (0)