Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: return Promises for Ti.Window open()/close() methods #12407

Merged
merged 12 commits into from Feb 8, 2021

Conversation

sgtcoolguy
Copy link
Contributor

@sgtcoolguy sgtcoolguy commented Jan 22, 2021

JIRA: #12407

Description:
Relates to #12349, #12407, #12417

This modifies some of our async methods to also return a Promise:

  • Ti.UI.Window.open()
  • Ti.UI.Window.close()
  • Ti.UI.NavigationWindow.open()
  • Ti.UI.NavigationWindow.close()
  • Ti.UI.TabGroup.open()
  • Ti.UI.TabGroup.close()

Unit tests are included!

@sgtcoolguy
Copy link
Contributor Author

After a lot of messing around my best guess here is that internal code is calling window close, and is not "consuming" the promise generated (because it's not actually surfaced to the JS engine/user) and JSC is freaking out because we have unhandled promise rejections/fulfillments. So... I'm not sure what the "fix" here would be. Perhaps to explicitly delineate the JS-facing method from the method to be used internally? Flag in some way so the KrollPromise creates a dummy variant that doesn't generate a real JS Promise when init from an internal API call and not the JS facing API surface?

@build build added this to the 10.0.0 milestone Jan 22, 2021
@build build requested review from a team January 22, 2021 20:56
first.then(() => {
const second = tabGroup.open();
second.should.be.a.Promise();
second.then(() => finish(new Error('Expected second #open() to be rejected'))).catch(_e => finish());
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.tabgroup.test.js line 468 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.tabgroup.test.js line 468 – Avoid nesting promises. (promise/no-nesting)

backgroundColor: '#0000ff'
});

win.open().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.window.test.js line 718 – Each then() should return a value or throw (promise/always-return)

@build
Copy link
Contributor

build commented Jan 22, 2021

Fails
🚫

Test suite crashed on iOS simulator. Please see the crash log for more details.

🚫 Tests have failed, see below for more information.
Warnings
⚠️

tests/Resources/ti.ui.navigationwindow.test.js#L268 - tests/Resources/ti.ui.navigationwindow.test.js line 268 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

⚠️

tests/Resources/ti.ui.navigationwindow.test.js#L268 - tests/Resources/ti.ui.navigationwindow.test.js line 268 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

⚠️

tests/Resources/ti.ui.tabgroup.test.js#L22 - tests/Resources/ti.ui.tabgroup.test.js line 22 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

⚠️

tests/Resources/ti.ui.tabgroup.test.js#L22 - tests/Resources/ti.ui.tabgroup.test.js line 22 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

Messages
📖 ✊ The commits in this PR match our conventions! Feel free to Rebase and Merge this PR when ready.
📖 ❌ 4 tests have failed There are 4 tests failing and 460 skipped out of 9560 total tests.

Tests:

ClassnameNameTimeError
android.emulator.Titanium.UI.NavigationWindowbasic open/close navigation (5.0.2)10.004
Error: timeout of 10000ms exceeded
at Titanium.<anonymous> (/ti-mocha.js:6535:53723)
android.emulator.Titanium.UI.Windowwindow_navigation (5.0.2)30.102
Error: timeout of 30000ms exceeded
at Titanium.<anonymous> (/ti-mocha.js:6535:53120)
android.emulator.Titanium.UI.View"after all" hook (5.0.2)23.066
Error: timeout of 10000ms exceeded
at Titanium.<anonymous> (/ti-mocha.js:6535:53723)
android.emulator.Titanium.UI.View"after each" hook (5.0.2)13.032
Error: timeout of 10000ms exceeded
at Titanium.<anonymous> (/ti-mocha.js:6535:53723)

Generated by 🚫 dangerJS against 1566bbd

@hansemannn
Copy link
Collaborator

hansemannn commented Jan 22, 2021

@sgtcoolguy Why would you add a tab group to a navigation window? They are both top-level containers and not meant to be added to each other, which would explain the crash. I hope that helps :)

@sgtcoolguy
Copy link
Contributor Author

@hansemannn developers do crazy things. It's in our test suite as a regression check because we have customers who have that code!

Anyways, I narrowed this down eventually. The crash is specifically coming from when the old style TiProxy hierarchy gets KrollFinalizer called on some object. At that point trying to use the JSC API to create a new Promise or Error crashes. So I basically have to toggle a global static BOOL while KrollFinalizer is happening and be sure not to try and actually create a "real" Promise while it is (basically make the Promise a no-op under the hood - create nothing, do nothing when rsolve/reject are called).

The second thing which was my initial hunch is that if I create a Promise under the hood in obj-c that doesn't get exposed out to the developer (because say we call open/close on a window from internal Tab code) and we do not handle the rejection/fulfillment of the Promise the engine gets wonky. I don't think this causes a crash, but it does cause an unhandled promise rejection error to get set on the context and there's no official API to actually handle that yet (they have a new method on main branch now, but it's unreleased). So I needed to add a way to "flush" internal Promises so that we handle them internally with noop handlers. I assume with a lot of playing around perhaps we could hack a way to detect this and fire off an unhandledpromiserejection event ala Node/V8. I'm not entirely sure yet (like when do we check for the error, how do we expose it, how do we "reset" it).

@hansemannn
Copy link
Collaborator

Customers … 😄

tabGroup.addTab(tabB);

const openPromise = tabGroup.open();
openPromise.then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.tabgroup.test.js line 399 – Each then() should return a value or throw (promise/always-return)


const first = nav.open();
first.should.be.a.Promise();
first.then(() => nav.open(), e => finish(e)).then(() => finish(new Error('Expected second #open() to be rejected')), _e => finish());
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 100 – Expected catch() or return (promise/catch-or-return)

});

const openPromise = win.open();
openPromise.then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.window.test.js line 696 – Each then() should return a value or throw (promise/always-return)

openPromise.then(() => {
const result = win.close();
result.should.be.a.Promise();
result.then(() => finish()).catch(e => finish(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.window.test.js line 699 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.window.test.js line 699 – Avoid nesting promises. (promise/no-nesting)


const first = win.open();
first.should.be.a.Promise();
first.then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.window.test.js line 752 – Each then() should return a value or throw (promise/always-return)

@sgtcoolguy sgtcoolguy mentioned this pull request Jan 26, 2021
@sgtcoolguy sgtcoolguy changed the title Window promises feat: return Promises for Ti.Window open()/close() methods Jan 26, 2021
nav = Ti.UI.createNavigationWindow({ window: Ti.UI.createWindow() });

nav.open().then(() => {
nav.close().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 68 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 68 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 68 – Each then() should return a value or throw (promise/always-return)


nav.open().then(() => {
nav.close().then(() => {
nav.close().then(() => finish(new Error('Expected second #close() call on Window to be rejected'))).catch(e => finish());
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 69 – 'e' is defined but never used. Allowed unused args must match /^_.+/u. (no-unused-vars)
  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 69 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 69 – Avoid nesting promises. (promise/no-nesting)

openPromise.then(() => {
const result = tabGroup.close();
result.should.be.a.Promise();
result.then(() => finish()).catch(e => finish(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.tabgroup.test.js line 402 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.tabgroup.test.js line 402 – Avoid nesting promises. (promise/no-nesting)

openPromise.then(() => {
const result = nav.close();
result.should.be.a.Promise();
result.then(() => finish()).catch(e => finish(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 52 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 52 – Avoid nesting promises. (promise/no-nesting)


const result = nav.open();
result.should.be.a.Promise();
result.then(() => finish(), e => finish(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 92 – Expected catch() or return (promise/catch-or-return)

first.then(() => {
const second = win.open();
second.should.be.a.Promise();
second.then(() => finish(new Error('Expected second #open() to be rejected'))).catch(_e => finish());
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.window.test.js line 755 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.window.test.js line 755 – Avoid nesting promises. (promise/no-nesting)

@sgtcoolguy sgtcoolguy marked this pull request as ready for review January 29, 2021 17:29
nav = Ti.UI.createNavigationWindow({ window: Ti.UI.createWindow() });

const openPromise = nav.open();
openPromise.then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 49 – Each then() should return a value or throw (promise/always-return)

it('called twice on Window rejects second Promise', finish => {
nav = Ti.UI.createNavigationWindow({ window: Ti.UI.createWindow() });

nav.open().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.navigationwindow.test.js line 67 – Each then() should return a value or throw (promise/always-return)


win.open().then(() => {
win.close().then(() => {
win.close().then(() => finish(new Error('Expected second #close() call on Window to be rejected'))).catch(e => finish());
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.window.test.js line 720 – 'e' is defined but never used. Allowed unused args must match /^_.+/u. (no-unused-vars)
  • ⚠️ tests/Resources/ti.ui.window.test.js line 720 – Avoid nesting promises. (promise/no-nesting)
  • ⚠️ tests/Resources/ti.ui.window.test.js line 720 – Avoid nesting promises. (promise/no-nesting)

@drauggres
Copy link
Contributor

I don't think this is a good idea overall. Even though I like Promises, they don't fit here.
And I think this will lead to problems with "unhandled promises" in v8 too.

@sgtcoolguy
Copy link
Contributor Author

@drauggres Why specifically do you disagree here?

In doing this implementation, it struck me by how often we actually have hidden cases of failures or "ignoring" the open/close calls we do where we simply spit something to the logs (or not even that!) and there's no means of determining concretely whether or not the open or close call worked. In the past developers are expected to hang event listeners to get notified if they do get opened/closed - but there was really no mechanism for handling failures specifically.

I do think this will force us to deal with the overall problem of generic unhandled promise rejections - which we'll run into regardless of this PR, but obviously with lots of pre-existing code out there this would quickly trigger for existing apps. But presuming we can hook a global event and default listener to log unhandled promises, this should actually help developers pinpoint where they may have open/close calls occurring in bad states or with timing issues.

done();
});
win.close();
win.close().then(() => done()).catch(_e => done());
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ tests/Resources/ti.ui.view.test.js line 31 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)
  • ⚠️ tests/Resources/ti.ui.view.test.js line 31 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

@sgtcoolguy sgtcoolguy merged commit 21553ef into tidev:master Feb 8, 2021
@sgtcoolguy sgtcoolguy deleted the window-promises branch February 8, 2021 18:57
@hansemannn
Copy link
Collaborator

hansemannn commented Mar 15, 2021

This has caused TIMOB-28394. Reverting this commit fixes the issue. Please go through a proper code-review :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants