Detecting “returned promise only” status of an async function

问题: I have a situation like this: async function thirdPartyCode(a) { if (a == ...) { return myPromiser(...) // can allow and act on this } let b = await...

问题:

I have a situation like this:

async function thirdPartyCode(a) {
    if (a == ...) {
        return myPromiser(...)  // can allow and act on this
    }
    let b = await someoneElsesPromiserB(...)
    if (b == ...) {
        let c = await myPromiser(...)  // must error on this
        ...
    }
    let d = await someoneElsesPromiserD(...)
    let e = myPromiser(...)  // note no await
    return e  // can allow and act on this
}

As the author of myPromiser() and caller of this thirdPartyCode(), I'd like to detect whether the myPromiser()'s promise is used as the returning promise of the async function. This is the only legal way to use it in this particular kind of async function's calling context. It cannot be awaited on, or have .then() clauses attached to it while it is inside this function.

If there were a way to know "When is the body of an async function actually finished", that would be a wedge to solving it.

(Note: The strange limitations in this question are a by-product of using the Emscripten Emterpreter. The limits may (?) need not apply when simulated pthreads are available via WebAssembly workers / SharedArrayBuffer / etc. But those bleeding-edge browser features aren't enabled by default at time of writing...so this unusual desire comes from wanting a compatible subset of code to be legal.)


回答1:

UPDATE See comments from @Bergi below: While this approach can mostly work, the then() and catch() "promise like appearance" to provide better errors breaks it. However, leaving them here to help illustrate what the actual desire is.

RE: "If there were a way to know 'When is the body of an async function actually finished'"

Async functions are "actually finished" when their returning promise resolves. If you control the calling context and myPromiser(), then you (er, me) could choose to make myPromiser() not return a promise directly -but- a Promise-like object which memoizes the work you intend to do once the call is finished. Give it a fake promise interface with .then() and .catch() clauses...but you can still recognize it with instanceof in the handler that called thirdPartyCode() and treat it as an instruction to do the work.

Making the memoization an Error subclass seems like it could be a good thing--so it identifies the calling stack and can implicate offending callsites like the await myPromiser(...) from the example.

class MyFakePromise extends Error {
   memo  // capture of whatever MyPromiser()'s args were for
   constructor(memo) {
       super("You can only use `return myPromiser()` in this context")
       this.memo = memo
   }
   errorAndCleanup() {
       /* this.memo.cleanup() */  // if necessary
       throw this  // will implicate the offending `myPromiser(...)` callsite
   }
   then(handler) {  // !!! See UPDATE note, can't improve errors via .then()
       this.errorAndCleanup()
   }
   catch(handler) {  // !!! See UPDATE note, can't improve errors via .catch()
       this.errorAndCleanup()
   }
}

This gives the desired property of erroring for anyone who tried to actually use it:

 > let x = new MyFakePromise(1020)
 > await x
 ** Uncaught (in promise) Error: You can only use `return myPromiser()` in this context

But if it's not used and just passed on, you can treat it like data. So then you'd do something like this in the calling context where fake promises must be used:

fake_promise_mode = true

thirdPartyCode(...)
   .then(function(result_or_fake_promise) {
       fake_promise_mode = false
       if (result_or_fake_promise instanceof MyFakePromise) {
          handleRealResultMadeFromMemo(result_or_fake_promise.memo)
       else
          handleRealResult(result_or_fake_promise)
   })
   .catch(function(error)) {
       fake_promise_mode = false
       if (error instanceof MyFakePromise)
           error.errorAndCleanup()
       throw error
   })

And myPromiser() would heed the flag to know if it had to give the fake promise:

function myPromiser(...) {
    if (fake_promise_mode) {
        return new MyFakePromise(...memoize args...)
    return new Promise(function(resolve, reject) {
        ...safe context for ordinary promising...
    })
}
  • 发表于 2019-03-17 06:53
  • 阅读 ( 97 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除