Skip to content

Typescript generator function assumes asynchronous global Promise #19909

Closed
@fluffynuts

Description

@fluffynuts

This may sound like a silly report -- please bear with me (:

I'm the author of synchronous-promise, an A+ Promise-like implementation which doesn't defer (and allows pausing and resuming), so makes testing certain scenarios clearer / easier. Mostly, it's worked well with TypeScript because of how awesome TypeScript's async/await logic is.

The problem is very specific is this: if SynchronousPromise is installed globally (overriding Promise -- and we could do this so that client code returning a Promise actually returns a SynchronousPromise, during test-time), and a SynchronousPromise resolution happens in a setTimeout, the TS generator function also makes use of SynchronousPromise and doesn't complete awaiting the following:

import { SynchronousPromise } from "./index";
import { expect } from 'chai';

// install SynchronousPromise globally
global.Promise = SynchronousPromise;

describe("typescript async/await", () => {
  it("should not hang", async function() {
    // Arrange
    // Act
    await new SynchronousPromise(function(resolve, reject) {
      setTimeout(() => {
        resolve("done!");
      }, 0);
    });
  })
});

The problem seems to track down to the generated generator code which expects a P (short-handed global Promise) to defer. If I comment out the line:

// global.Promise = SynchronousPromise;

then the test completes, as expected.

Now, I realise that there is a valid argument that SynchronousPromise, being not async in nature, violates the A+ specification and that this whole issue shouldn't be the problem of TypeScript at all. This is a fair argument, which has a counter-argument: that all TypeScript is a super-set of Javascript. The following Javascript works fine:

"use strict";
var
  expect = require("chai").expect,
  sut = require("./index"),
  SynchronousPromise = sut.SynchronousPromise,
describe("with timeout in ctor", () => {
  it("should complete when the timeout does", (done) => {
    // Arrange
    var
      captured,
      sut = new SynchronousPromise(function(resolve, reject) {
        setTimeout(function() {
          resolve("moo");
        }, 0);
      }).then(function(result) {
        captured = result;
      });
    // Act
    // Assert
    setTimeout(function() {
      expect(captured).to.equal("moo");
        done();
     }, 500);
  });
});

And debugging the prior snippet has shown that all promises (even the TS-generated / wrapper ones created for async/await) are resolved. Execution is simply not continued by the generator.

Expected behavior:
The first snippet (TypeScript) should complete like the second snippet (Javascript)

Actual behavior:
The Javascript completes where the TypeScript does not

Proposed solutions.

  1. Since the generator function requires deferral, perhaps use setTimeout -- however, this is subject to the same problems as testing frameworks like jasmine can install a testing clock which has to be manually advanced by the test code.
  2. "var off" the global Promise implementation before allowing client code to run and use that version inside the generator code. This seems to be the lowest-hanging fruit for solving this problem, from my point of view.

Of course, the response could also be that this is just an issue with synchronous-promise, but it also highlights that TS compiled execution can be interfered with by client code -- which may not be optimal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions