Ad
  • Custom User Avatar

    Can I get these issues resolved, perhaps?

  • Custom User Avatar

    Tests should be a little more logical now

  • Custom User Avatar

    Random tests added

  • Custom User Avatar

    Aha, I get you! Thank you for laying it out. I can't think of a way to test that an error is not thrown in place while simultaneously handling the unhandled rejection. The best I could come up with is Should handle thrown errors as rejections where it tests that an error thrown in the generator is turned into a rejection.

    Luckily, I can get a passing test for Should not throw in place and Codewars doesn't much care about unhandled rejections.
    I've also randomised much of the data in the test fixture.

    As for async generators, I specifically wanted to emulate async/await with plain old generators. I should emphasise that in the description!
    I could perhaps still do a better job of explaining in description; is there anything else I'm missing?

    Thanks a lot for your assistance on my first kata.

    EDIT: Oh I just thought of some nicer ways to randomise that would help prevent some of the brute forcing. Will update shortly.

  • Custom User Avatar

    The fundamental problem here is, the kata's premise is flawed: synchronous and asynchronous JS handle exceptions being thrown in place differently. When a non-async function throws in place it throws an exception in the current execution context; this cannot happen in async function as the entire function is assumed to be wrapped in a Promise.

    This has very, very, very important ramifications on how to handle exceptions throwing from them. Synchronous exception are handled via try/catch. Asynchronous exception can only be handled via .catch. And you can only turn the former into the latter, but not the opposite:

    function f1() {
      throw new Error('f1');
    }
    async function f2() {
      throw new Error('f2');
    }
    // f2 is almost equivalent to:
    function f3() {
      return new Promise((resolve, reject) => { throw new Error('f3'); });
    }
    
    // this exception is handled because it's a synchronous exception
    try { f1(); } catch(ex) { console.log(ex); }
    
    // this doesn't work because it's asynchronous; the exception "passes through" try-catch block and becomes an unhandled promise rejection.
    try { f2(); } catch(ex) { console.log(ex); }
    
    // this is the correct way of handling asynchronous exceptions, with .catch
    f2().catch(ex => { console.log(ex); });
    
    // this is how you turn a synchronous exception into an asynchronous one (observe how it looks the same to f3)
    // note that the opposite is impossible, try/catch block can never catch promise rejection at call site
    new Promise((resolve, reject) => { f1(); }).catch(ex => { console.log(ex); });
    

    Similarly, asynk is taking a generator that yields Promises rather than an AsyncGenerator, which also has this difference:

    function* g1() {
      throw new Error('g1');
    }
    async function* g2() {
      throw new Error('g2');
    }
    
    g1().next(); // this throws an error in place
    g2().next(); // this returns a rejected Promise
    
    // etc...
    

    More technically, code inside async/Promise isn't run in the same "context": every async/Promise queues the function in the event loop to be run in a different "context" later. So the current expectation from the kata is really unsound inside the JS runtime model, and is definitely incorrect.


    And besides all this, it doesn't help that the description only says "behave as similarly to async/await as is feasible", which doesn't explain how everything as mentioned above should work. Fixed tests only give part of the spec; the other part is in the test fixture, which nobody can see.

  • Custom User Avatar

    Hello, yes asynk is supposed to return a Promise whether the last yielded/returned value from the generator is a Promise or not. The generator yields Promises to the asynk function. You're right it's not an async generator, it's just a generator.

    The generator can return a Promise too, just like async functions can return a Promise. Or they can return values which are wrapped in Promises. Both of these async functions returns a Promise of 1:

    async function hello1() {
    	return 1;
    }
    async function hello2() {
    	return Promise.resolve(1);
    }
    

    I want to highlight that the yields shouldn't be returned from asynk. Both of these should give you a Promise of undefined:

    const y = async () => {
    	await Promise.resolve(1);
    };
    
    y().then(console.log);
    
    const x = asynk(function* () {
    	yield Promise.resolve(1);
    });
    
    x.then(console.log);
    

    I will, however, update the tests to be more logical, and since async functions allow you to await a non-Promise for some reason, perhaps I should allow that too?

    Please give me some pointers, as I'm new to writing tests.

  • Custom User Avatar
  • Custom User Avatar

    The kata design is illogical:

    	it("Should return a Promise if a Promise is yielded", () => {
    		const result = asynk(function* () {
    			yield Promise.resolve(1);
    		});
    		expect(result).toBeInstanceOf(Promise);
    	});
    	it("Should throw an error if a non-Promise is yielded", () => {
    		const result = () =>
    			asynk(function* () {
    				yield 1;
    			});
    		expect(result).toThrow();
    	});
    

    If asynk is supposed to be async, it should return a Promise and be handled in usual ways (.then/.catch). Async errors aren't thrown to the current point of execution.

    A generator returning a Promise isn't async code at all; it is a generator, not an async generator. The execution point stays the same scope, and returns to the same place when something is yielded/returned from the generator.

    So why is the generator returning Promises? Does the kata author even know how async/await and generators work?