diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index ec7d683404e..247b3e2c606 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -109,19 +109,20 @@ const waitPromise = function*(promise) { const thread = globalState.thread; let returnValue; + // enter STATUS_PROMISE_WAIT and yield + // this will stop script execution until the promise handlers reset the thread status + // because promise handlers might execute immediately, configure thread.status here + thread.status = 1; // STATUS_PROMISE_WAIT + promise .then(value => { returnValue = value; thread.status = 0; // STATUS_RUNNING - }) - .catch(error => { + }, error => { thread.status = 0; // STATUS_RUNNING globalState.log.warn('Promise rejected in compiled script:', error); }); - // enter STATUS_PROMISE_WAIT and yield - // this will stop script execution until the promise handlers reset the thread status - thread.status = 1; // STATUS_PROMISE_WAIT yield; return returnValue; diff --git a/test/fixtures/tw-block-returning-promise-like.sb3 b/test/fixtures/tw-block-returning-promise-like.sb3 new file mode 100644 index 00000000000..d7c232ca21a Binary files /dev/null and b/test/fixtures/tw-block-returning-promise-like.sb3 differ diff --git a/test/integration/tw_block_returning_promise_like.js b/test/integration/tw_block_returning_promise_like.js new file mode 100644 index 00000000000..a0e95e8c3e7 --- /dev/null +++ b/test/integration/tw_block_returning_promise_like.js @@ -0,0 +1,65 @@ +const {test} = require('tap'); +const fs = require('fs'); +const path = require('path'); +const VirtualMachine = require('../../src/virtual-machine'); +const Scratch = require('../../src/extension-support/tw-extension-api-common'); + +// based on https://github.com/TurboWarp/scratch-vm/issues/184 + +class TestExtension { + getInfo () { + return { + id: 'testextension', + name: 'test', + blocks: [ + { + opcode: 'test', + blockType: Scratch.BlockType.COMMAND, + text: 'test block' + } + ] + }; + } + test () { + // returns a PromiseLike that calls handler immediately. + const promise = { + then (callbackFn) { + callbackFn(); + return promise; + } + // intentionally omit catch() as that is not part of PromiseLike + }; + return promise; + } +} + +const fixture = fs.readFileSync(path.join(__dirname, '../fixtures/tw-block-returning-promise-like.sb3')); + +for (const compilerEnabled of [false, true]) { + test(`handles blocks that return a promise-like object - ${compilerEnabled ? 'compiled' : 'interpreted'}`, t => { + const vm = new VirtualMachine(); + vm.extensionManager.addBuiltinExtension('testextension', TestExtension); + + vm.setCompilerOptions({ + enabled: compilerEnabled + }); + t.equal(vm.runtime.compilerOptions.enabled, compilerEnabled, 'sanity check'); + + vm.loadProject(fixture).then(() => { + let ended = 0; + vm.runtime.on('SAY', (target, type, text) => { + if (text === 'end') { + ended++; + } else { + t.fail('said something unknown'); + } + }); + + vm.greenFlag(); + vm.runtime._step(); + + t.equal(ended, 1, 'script ran once immediately'); + t.end(); + }); + }); +}