【问题标题】:Return a promise from an executor function?从执行器函数返回一个承诺?
【发布时间】:2017-05-20 04:37:39
【问题描述】:

尝试与 JS API 交互,但被 Grunt 任务运行时失败;我觉得我的逻辑很混乱。我的步骤:

  • 从文件中获取令牌,检查它们 (check_tokens)
  • 如果它们是旧的 - 刷新它们 (refresh_tokens)
  • 调用 API 刷新,如果失败 - 获取新的 (authorize_with_api)
  • 来自authorize_with_api 错误拒绝或使用令牌解决

目前,Grunt 任务报告 UnhandledPromiseRejectionWarning 并且永远不会完成。如果我注释掉对authorize_with_api 的调用,它会正确退出并出现错误,我会打印出最上面的caught error! 消息。

为什么我不能从 executor 函数中返回一个 Promise?我的逻辑有什么问题?

/* global sdk, config, tokens */
return getTokens().then((p_tokens) => {
    tokens = p_tokens;
    return check_tokens(tokens);
}).then((tokens) => {
    console.log('then() is called!');
}).catch((err) => {
    console.error('caught error!', err);
}); 

function check_tokens(tokens) {
    if(are_old(tokens)) { // returns true
        return refresh_tokens(tokens);
    }
    return Promise.resolve(tokens);
}

function refresh_tokens(tokens) {
    return new Promise(function(resolve, reject) {
        sdk.refreshTokens(tokens.refresh_token, function(err, new_tokens) {
            if(err) {
                if(error.code === 'invalid_grant') {
                    return authorize_with_api();
                }
                reject('refreshTokens failed');
            } else if(newTokens) {
                resolve(new_tokens);
            } 
        });
    });
}

function authorize_with_api() {
    return new Promise(function(resolve, reject) {
        sdk.getTokens(config.auth_code, function(err, tokens) {
            if(err) {
                reject('getTokens failed');
            } else if(tokens) {
                resolve(tokens);
            } 
        });
    });
}

【问题讨论】:

  • tokens 似乎在authorize_with_api 内部未定义,您是要传入它吗?显示调用它的 gulp 任务可能会有所帮助
  • 请避免做出自己的承诺。已编写库来解决此任务,请使用其中之一。例如,bluebird 可以为您完成。 bluebirdjs.com/docs/api/promise.promisifyall.html
  • @Tomalak 你能进一步解释一下吗? OP 使用的是原生 Promises?
  • @Tomalak 我对此并不精通 - 你能指出我现在在哪里“承诺” - 通过将 SDK 调用包装到 Promises 中?它有什么问题?
  • 将 non-promise API 转换为 Promise API 并非易事,很容易出错(在这种情况下,OP 忘记使用 try/catch)。我不是说 OP 应该切换到 bluebird,我是说 OP 应该使用经过测试的库来实现承诺。

标签: javascript promise ecmascript-6 es6-promise


【解决方案1】:

从 Promise 构造函数(或其中的任何函数)返回不会解析 Promise:

return new Promise(function(resolve, reject) {
  sdk.refreshTokens(..., function(err, new_tokens) {
    if(error.code === 'invalid_grant') {
      return authorize_with_api();
    } // ^--- this will not chain to the promise being created.

即使您没有从 sdk.refreshTokens 回调中获得返回,而是在没有回调的情况下直接获得了 return authorize_with_api(),结果仍然不会被链接。

要解决一个承诺,您不能从它的构造函数返回,而是必须显式调用给定的回调之一(解决/拒绝):

return new Promise(function(resolve, reject) {
  sdk.refreshTokens(..., function(err, new_tokens) {
    if(error.code === 'invalid_grant') {
      resolve(authorize_with_api());
    } // ^--- must call resolve here

解决一个承诺实际上也处理了拒绝,因此无论authorize_with_api 解决还是拒绝,状态都会相应地向上传播。

我的建议是仍然保留 return 语句以保持 if 分支的预期视觉语义条件提前返回,但代码将在没有它的情况下工作,因为 Promises can only be resolved once 和所有进一步调用 reject/ resolve 被忽略。

return new Promise(function(resolve, reject) {
  sdk.refreshTokens(..., function(err, new_tokens) {
    if(error.code === 'invalid_grant') {
      return resolve(authorize_with_api());
    } // ^--- should still return here for readability - clean logic purposes
    reject('refreshTokens failed'); // this will be ignored if the above `resolve` gets called first, no matter if you have the `return` statement

例子:

function success() {
  return Promise.resolve('success');
}

function error() {
  return Promise.reject('error');
}

function alwaysPending() {
  return new Promise(() => {
    return success();
  });
}

function resolves() {
  return new Promise((resolve) => {
    resolve(success());
  });
}

function rejects() {
  return new Promise((resolve) => {
    resolve(error());
  });
}

alwaysPending().then(console.log); // doesn't log anything 
resolves().then(console.log);
rejects().catch(console.log);

【讨论】:

  • 很好的解释! 脱帽
  • 很高兴帮助队友:)
  • 请注意,他甚至不是来自Promise 执行器函数的return,而是来自其中某处的异步回调。
【解决方案2】:

只写最后一个 if 的 else 语句:

function authorize_with_api() {
    return new Promise(function(resolve, reject) {
        sdk.getTokens(config.auth_code, function(err, tokens) {
            if(err) {
                reject('getTokens failed');
            } else if(tokens) {
                resolve(tokens);
            } else {
                reject('tokens == undefined && err == undefined');
            }
        });
    });
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-01
    • 2017-04-05
    • 1970-01-01
    • 2015-11-27
    • 2015-04-27
    相关资源
    最近更新 更多