将 catch 块添加到 Promise 会返回待处理而不是被拒绝

Posted

技术标签:

【中文标题】将 catch 块添加到 Promise 会返回待处理而不是被拒绝【英文标题】:Adding a catch block to a promise returns pending instead of rejected 【发布时间】:2021-01-15 15:33:12 【问题描述】:

我有一个正在运行的 apollo graphql express 服务器。唯一的问题是 express 抱怨我在用来验证 jwt 令牌的 Promise 上没有 catch 块:

(node:96074) UnhandledPromiseRejectionWarning:未处理的承诺拒绝。此错误源于在没有 catch 块的情况下抛出异步函数内部,或拒绝未使用 .catch() 处理的承诺

我可以在 promise 中添加一个 catch 块,但是当令牌失效时它会返回 pending 而不是 rejected。这会导致身份验证流程中断,因为我的 graphql 解析器依赖该拒绝来阻止对数据库的访问。

这就是我用于身份验证的 auth0 建议设置它的方式。他们只是没有提到 UnhandledPromiseRejectionWarning。

代码如下所示:

//server def
const server = new ApolloServer( 
    typeDefs, 
    resolvers, 
    context: ( req ) => 
        if (req.headers.authorization) 
            const token = req.headers.authorization.split(' ')[1];

            //THE PROMISE IN QUESTION
            const authUserObj = new Promise((resolve, reject) => 
                jwt.verify(token, getKey, options, (err, decoded) => 
                    if (err) 
                        reject(err);
                    
                    if (decoded) 
                        resolve(decoded); 
                    
                );
            );

            return 
                authUserObj
            ;
        
    ,
    introspection: true,
    playground: true
);

//a graphql resolver that gets the rejection via authUserObj and catches the error 
addUser: async (parent, args, authUserObj) => 
            try 
                const AuthUser = await authUserObj;
                const response = await User.create(args);
                return response;
             catch(err) 
                throw new AuthenticationError('You must be logged in to do this');
            
        

一切正常...除了我希望克服的那个烦人的节点错误!所以我在 promise 中添加了一个 catch 块:

 const authUserObj = new Promise((resolve, reject) => 
                jwt.verify(token, getKey, options, (err, decoded) => 
                    if (err) 
                        console.log("-------rejected-------", err.message)
                        reject(err);
                    
                    if (decoded) 
                        console.log("-------decoded-------")
                        resolve(decoded); 
                    
                );
            ).catch( err =>  return err.message);

现在 authUserObj 不是返回被拒绝,而是处于挂起状态,任何人都可以添加用户,这违背了 auth 的目的。

如果有人知道如何在仍然拒绝该错误的同时捕获该错误,我会全力以赴。谢谢。

【问题讨论】:

【参考方案1】:

问题不在于未处理的 Promise 拒绝,而更多地在于未处理的 Promise。您尝试在 context 对象中放入一个承诺,然后在 addUser 解析器中放入 await 承诺。在其他解析器中,promise 可能根本不会被使用,并且当 jwt 验证失败时,拒绝将不会被处理。 (另外,如果解析器是异步执行的,promise 可能会在它们处理它之前被拒绝。

相反,whole context initialisation 应该异步完成,返回带有用户详细信息的上下文对象的承诺。这意味着请求甚至在开始执行查询之前就会失败:

const server = new ApolloServer( 
    typeDefs, 
    resolvers, 
    context: ( req ) => 
        if (req.headers.authorization) 
            const token = req.headers.authorization.split(' ')[1];
            return new Promise((resolve, reject) => 
                jwt.verify(token, getKey, options, (err, decoded) => 
                    if (err) reject(err);
                    else resolve(decoded);
                );
            ).then(authUser => 
                if (authUser) return  authUser ;
                // else return ;
            );
            // .catch(err =>  … ) - you may chose to ignore verification failure,
            // and still return a context object (without an `authUser`)
        
        // else return ; - when not sending the header, no token will be checked at all
    ,
    introspection: true,
    playground: true
);
// a graphql resolver that checks for the authUserObj
addUser: async (parent, args, authUserObj) => 
    if (!authUserObj)  // you might also want to check specific claims of the jwt
        throw new AuthenticationError('You must be logged in to do this');
    
    const response = await User.create(args);
    return response;

【讨论】:

好点,谢谢。我会这样做的。 @j1mmy 在这种情况下,您可能想接受我的回答。很高兴能提供帮助!【参考方案2】:

就像try/catch 一样,如果您只是从.catch() 处理程序返回一个正常值(或不返回任何内容),.catch() 会将承诺链从拒绝更改为已解决。当您返回“正常”值时,拒绝被视为“已处理”,并且承诺链将使用该新值解决。这就是您处理错误并继续正常处理的方式。

要保持 Promise 链被拒绝,您必须 throw 或返回被拒绝的 Promise。这将使承诺链保持被拒绝。

所以,如果你想让authUserObj 保持被拒绝的承诺,那么改变这个:

).catch( err =>  return err.message);

到这里:

).catch( err =>  return Promise.reject(err.message));

或类似的东西,要么引发错误,要么返回被拒绝的承诺。

【讨论】:

谢谢你说得非常清楚,并且有效。非常感谢。 这不是用于 apollo 服务器的正确解决方案。我会尽快写一个答案 另外,现在我再次阅读它,我不确定重新抛出错误将如何有助于避免未处理的拒绝......所以这根本不是一个解决方案。 @j1mmy - 这解释了你的问题的标题。提醒您,该标题是 将 catch 块添加到 Promise 会返回待处理而不是被拒绝

以上是关于将 catch 块添加到 Promise 会返回待处理而不是被拒绝的主要内容,如果未能解决你的问题,请参考以下文章

对不同类型的两个 Promise 使用相同的 then/catch/finally 块

Promise的特性

在 mongodb 连接 url 中将“useNewUrlParser”和“useUnifiedTopology”设置为 true,避免了 promise 函数中的 catch 块

Node.JS - 无法使用try / catch块获得异步抛出

async函数

es6 promise的catch 和 then 的区别认识