最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Return a promise from an executor function? - Stack Overflow

programmeradmin3浏览0评论

Trying to interact with a JS API, but it fails when run by a Grunt task; I think my logic is confused. My steps:

  • get tokens from file, check them (check_tokens)
  • if they are old - refresh them (refresh_tokens)
  • call API to refresh, if fails - get new ones (authorize_with_api) <- this is the issue
  • from authorize_with_api reject with error or resolve with tokens

Currently the Grunt task reports an UnhandledPromiseRejectionWarning and never pletes. If I ment out the call to authorize_with_api it then exits properly with an error, and I get my topmost caught error! message printed.

Why can't I return a promise out of an executor function? What's wrong with my logic?

/* 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);
            } 
        });
    });
}

Trying to interact with a JS API, but it fails when run by a Grunt task; I think my logic is confused. My steps:

  • get tokens from file, check them (check_tokens)
  • if they are old - refresh them (refresh_tokens)
  • call API to refresh, if fails - get new ones (authorize_with_api) <- this is the issue
  • from authorize_with_api reject with error or resolve with tokens

Currently the Grunt task reports an UnhandledPromiseRejectionWarning and never pletes. If I ment out the call to authorize_with_api it then exits properly with an error, and I get my topmost caught error! message printed.

Why can't I return a promise out of an executor function? What's wrong with my logic?

/* 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);
            } 
        });
    });
}
Share Improve this question edited Jul 14, 2018 at 15:20 nem035 35.5k6 gold badges92 silver badges104 bronze badges asked Jan 5, 2017 at 15:11 montrealistmontrealist 5,69312 gold badges50 silver badges72 bronze badges 8
  • tokens seems to be undefined inside authorize_with_api , did you mean to pass it in? Showing your gulp task that calls this may be helpful – Ruan Mendes Commented Jan 5, 2017 at 15:29
  • 1 Please avoid doing your own promisification. Libraries have been written to solve this task, use one of them. For example, bluebird can do it for you. bluebirdjs./docs/api/promise.promisifyall.html – Tomalak Commented Jan 5, 2017 at 15:30
  • 1 @Tomalak Can you explain further? The OP is using native Promises? – Ruan Mendes Commented Jan 5, 2017 at 15:53
  • @Tomalak I'm not well-versed in this - can you point out where am I "promisifying" now - by wrapping SDK calls into Promises? What's wrong with it? – montrealist Commented Jan 5, 2017 at 16:04
  • 1 Turning a non-promise API into a promise API is not trivial, mistakes are easily made (in this case, the OP forgot to use try/catch). I'm not saying the OP should switch to bluebird, I'm saying the OP should use a tested library that does the promisification. – Tomalak Commented Jan 5, 2017 at 16:04
 |  Show 3 more ments

2 Answers 2

Reset to default 11

Returning from a Promise constructor (or any function within it) does not resolve a 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.

Even if you didn't have the return from the sdk.refreshTokens callback and instead had a direct return authorize_with_api() without the callback, the result would still not get chained.

To resolve a promise, you cannot return from its constructor but must explicitly call one of the given callbacks (resolve/reject) instead:

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

Resolving a promise actually handles rejection as well so no matter if authorize_with_api resolves or rejects, the state will propagate up the chain accordingly.

My suggestion is to still keep the return statement to maintain the intended visual semantics of the if branch conditioning an early return but the code will work without it because Promises can only be resolved once and all further calls to reject/resolve are ignored.

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

Examples:

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);

Just write else statement of last if:

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');
            }
        });
    });
}
发布评论

评论列表(0)

  1. 暂无评论