Skip to content

Conversation

@bpasero
Copy link
Member

@bpasero bpasero commented Jun 20, 2018

We have seen issues when mixing native promises and WinJS promises in the past (e.g. the famous this._state.then is not a function #49177).

It is very easy to mix native promises and WinJS promises when using async imports like so:

return TPromise.timeout(0).then(() => import("something"));

To prevent this from happening this PR introduces a winJSRequire() method that is not using native promises at all, but WinJS directly.

@Microsoft/vscode since it touches lots of areas and raising awareness

@jrieken and @alexandrudima to review if that makes sense to you

Unfortunately I did not find a way to have the import type flow through in the method so the usage of this method unfortunately has to repeat the import value twice.

@bpasero bpasero added this to the June 2018 milestone Jun 20, 2018
@bpasero bpasero self-assigned this Jun 20, 2018
@bpasero bpasero requested review from alexdima and jrieken June 20, 2018 07:54
Copy link
Member

@jrieken jrieken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 as discussed

Copy link
Member

@alexdima alexdima left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally find the usage of an utility function to decrease code readability.

AFAICT, the following two snippets produce the same result:

const r1 = TPromise.wrap(import('vscode-textmate'));
const r2 = winJSRequire<typeof import('vscode-textmate')>('vscode-textmate');

AFAICT, they both return correctly an instance of a WinJS Promise:

image

That is what I was using before in TMSyntax.ts. I would suggest to go with the TPromise.wrap syntax and to write a TSLint rule that looks for usages of import and reminds us that we should use TPromise.wrap around them.

@bpasero
Copy link
Member Author

bpasero commented Jun 20, 2018

@alexandrudima

AFAICT, the following two snippets produce the same result

Well, in the one case a native promise is created and in the other not, so even though both might produce the same result, your solution still mixes native promises and WinJS promises.

I can see if TPromise.wrap(<native promise>) causes the same issue with this._state.then is not a function to verify that assumption.

@bpasero
Copy link
Member Author

bpasero commented Jun 26, 2018

@alexandrudima with TPromise.wrap(import('xyz')) I am still hitting an error this._state.then is not a function. Steps:

  • uncomment the 2 console.log statements in bulkEditService (here)
  • change doMoveItemToTrash to the implementation leveraging TPromise.wrap(import('electron')) (see below)
  • run scripts/test-integration.sh
  • see output appearing at the end ("TypeError: this._state.then is not a function", source: file:///Users/bpasero/Development/Microsoft/monaco/out/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.js (458))
private doMoveItemToTrash(resource: uri): TPromise<void> {
	const absolutePath = resource.fsPath;

	return TPromise.wrap(import('electron')).then(electron => {
		const result = electron.shell.moveItemToTrash(absolutePath);
		if (!result) {
			return TPromise.wrapError(new Error(isWindows ? nls.localize('binFailed', "Failed to move '{0}' to the recycle bin", paths.basename(absolutePath)) : nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath))));
		}

		this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));

		return void 0;
	});
}

This does not happen with my proposed solution that avoids mixing native promises with WinJS promises where this can happen if the native promise resolves directly (the electron import is already resolved). I think this issue could also reproduce with other node.js imports that are loaded multiple times in different locations where the second load finds the module already in the cache.

@bpasero bpasero modified the milestones: June 2018, July 2018 Jun 28, 2018
@alexdima
Copy link
Member

alexdima commented Jun 29, 2018

I can reproduce and here is a reduction:


Refinement 1

	let p1 = TPromise.wrap<number>(new Promise<number>(function(c, e) { c(1); }));
	Promise.all([p1]);
	// ==> Uncaught TypeError: this._state.then is not a function

Refinement 2: drop Promise.all

	let p1 = TPromise.wrap<number>(new Promise<number>(function(c, e) { c(1); }));
	let thenFunc = p1.then.bind(p1);
	setTimeout(() => {
		thenFunc(() => {
			console.log(`finished`);
		});
	}, 0);
	// ==> Uncaught TypeError: this._state.then is not a function

Refinement 3: do not use native Promise at all!

	let c;
	let p1 = new TPromise(function(_c, e) { c = _c; });
	let thenFunc = p1.then.bind(p1);
	setTimeout(() => {
		c(1);
		thenFunc(() => {
			console.log(`finished`);
		});
	}, 0);
	// ==> Uncaught TypeError: this._state.then is not a function

IMHO dropping native promises in imports will only cover a subset of refinement 3. This exception will occur anytime someone captures the then method before the winjs promise is resolved...

@alexdima
Copy link
Member

alexdima commented Jun 29, 2018

The PR #49034 from Christof actually fixes this and IMHO we should take that, as winjs is broken unrelated to native promises --- se refinement 3.

The root cause is that winjs overrides a promise's .then method with a different .then method at the time the promise is resolved. So, if anyone has captured the .then method they will have a stale version.

@bpasero
Copy link
Member Author

bpasero commented Jun 29, 2018

@chrmarti any reason you did not push your solution forward?

@alexdima
Copy link
Member

alexdima commented Jul 2, 2018

@bpasero I briefly discussed with @chrmarti on Friday, he was not sure if this was the right solution. But now we have more proof (beyond native promises) that the root cause is the then function being overwritten from the state.

I suggest we go forward and merge @chrmarti's PR in debt week.

@bpasero bpasero closed this Jul 2, 2018
@bpasero bpasero deleted the ben/winjsrequire branch July 2, 2018 09:14
@alexdima alexdima mentioned this pull request Jul 2, 2018
alexdima added a commit that referenced this pull request Jul 2, 2018
@github-actions github-actions bot locked and limited conversation to collaborators Mar 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants