Description
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.
- Since the generator function requires deferral, perhaps use
setTimeout
-- however, this is subject to the same problems as testing frameworks likejasmine
can install a testing clock which has to be manually advanced by the test code. - "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.