webcontainer-core icon indicating copy to clipboard operation
webcontainer-core copied to clipboard

AsyncLocalStorage doesn't work with async/await

Open Janpot opened this issue 2 years ago • 8 comments

Description of Bug

AsyncLocalStorage doesn't work well with async/await

Steps to Reproduce

  1. Go to https://stackblitz.com/edit/stackblitz-starters-pkxqds?file=index.mjs
    import { AsyncLocalStorage } from 'node:async_hooks';
    
    const asyncLocalStorage = new AsyncLocalStorage();
    
    asyncLocalStorage.run('hello', () => {
      console.log('store before:', asyncLocalStorage.getStore());
      setTimeout(() => {
        console.log('store after:', asyncLocalStorage.getStore());
      }, 1);
    });
    
    asyncLocalStorage.run('hello', async () => {
      console.log('store async before:', asyncLocalStorage.getStore());
      await new Promise((r) => setTimeout(r, 1));
      console.log('store async after:', asyncLocalStorage.getStore());
    });
    
  2. Run node index.mjs
  3. Observe output
    store before: hello
    store async before: hello
    store after: hello
    store async after: undefined
    
  4. Running the same script locally produces output
    store before: hello
    store async before: hello
    store after: hello
    store async after: hello
    
    (Running under Node.js v16.20.0 on MacOS)

Expected Behavior

AsyncLocalStorage in stackblitz works with async/await.

Janpot avatar Aug 30 '23 09:08 Janpot

Hi @Janpot ! Thanks for opening this issue!

You are correct! AsyncLocalStorage does not currently works in StackBlitz. This is because we don't really have an implementation for async hooks and promise hooks. We have ideas about how to do that but we want to make sure it wouldn't impact performance too much.

We'll keep this issue open and keep you posted!

Nemikolh avatar Aug 30 '23 10:08 Nemikolh

Thank you @Nemikolh. Are there any recommended ways to detect whether my package is running under Stackblitz?

Janpot avatar Aug 30 '23 12:08 Janpot

Yes! You can use @webcontainer/env. It exposes a function isWebContainer() that works both on Node and in WebContainer.

Nemikolh avatar Aug 30 '23 13:08 Nemikolh

Related to https://github.com/nuxt/nuxt/issues/23032, it would be nice that if AsyncLocalStorage mock shows a one time warning while we are waiting for AsyncContext native support in browsers so that framework users implicitly relying on this API, consider that it is an unsupported platform feature instead of silently getting and error.

pi0 avatar Sep 06 '23 15:09 pi0

Is there any update on this?

birkskyum avatar Jan 04 '24 14:01 birkskyum

@birkskyum Last time I checked with the team, it needed native browser support. The best thing we might hope is to wait for AsyncContext proposal to move forward and browsers to implement it.

pi0 avatar Jan 04 '24 14:01 pi0

In the proposal it states:

Furthermore, the async/await syntax bypasses the userland Promises and makes it impossible for existing tools like Zone.js that instruments the Promise to work with it without transpilation.

@mgechev do you have any idea about the horizon for Angular to sunset/revamp zone.js? I've struggled with it not being compatible with modern ts compile targets, so it's holding the ecosystem back in multiple dimensions, now all the way to the tc39 proposals well beyond angular.

birkskyum avatar Jan 04 '24 14:01 birkskyum

Has anyone been able to get a polyfill working? I've tried this plugin I built, but still no luck:

function alsShim() {
  return {
    name: 'virtual-async-hooks',
    resolveId(id) {
      if (id === '\0virtual:async_hooks') return id;
    },
    load(id) {
      if (id !== '\0virtual:async_hooks') return null;

      console.log('loaded!', id);

      // Browser-friendly shim
      return `
        import AsyncContext from "@webfill/async-context";

        console.log('hello!')

        export class AsyncLocalStorage {
          #var = new AsyncContext.Variable({ defaultValue: undefined });
          
          run(store, fn, ...args) {
            console.log('Run', store)
            return this.#var.run(store, fn, ...args);
          }
          getStore() {
            console.trace('Get Store', this, this.#var.get())
            return this.#var.get();
          }
          enterWith(store) {
            this.#var.run(store, () => {});
          }
          disable() {}
          enable() {}
        }

        export default { AsyncLocalStorage };
      `;
    },
  };
}

tannerlinsley avatar Sep 06 '25 22:09 tannerlinsley