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

[rfc]: deprecate RSVP.Promise #7880

Merged
merged 16 commits into from
Mar 31, 2022
14 changes: 7 additions & 7 deletions packages/-ember-data/tests/integration/store-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { run } from '@ember/runloop';
import { settled } from '@ember/test-helpers';
import Ember from 'ember';

import { module, test } from 'qunit';
import { Promise, resolve } from 'rsvp';
Expand All @@ -9,6 +8,7 @@ import DS from 'ember-data';
import { setupTest } from 'ember-qunit';

import RESTAdapter from '@ember-data/adapter/rest';
import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';
import JSONAPISerializer from '@ember-data/serializer/json-api';
import RESTSerializer from '@ember-data/serializer/rest';
import deepCopy from '@ember-data/unpublished-test-infra/test-support/deep-copy';
Expand Down Expand Up @@ -132,7 +132,7 @@ module('integration/store - destroy', function (hooks) {
});

testInDebug('find calls do not resolve when the store is destroyed', async function (assert) {
assert.expect(2);
assert.expect(3);

let store = this.owner.lookup('service:store');
let next;
Expand All @@ -153,11 +153,6 @@ module('integration/store - destroy', function (hooks) {

this.owner.register('adapter:application', TestAdapter);

// needed for LTS 2.16
Ember.Test.adapter.exception = (e) => {
throw e;
};

store.shouldTrackAsyncRequests = true;
store.push = function () {
assert('The test should have destroyed the store by now', store.isDestroyed);
Expand All @@ -180,6 +175,11 @@ module('integration/store - destroy', function (hooks) {
// to flush, potentially pushing data into the store
await settled();
assert.ok(true, 'we made it to the end');

if (DEPRECATE_RSVP_PROMISE) {
assert.expectDeprecation({ id: 'ember-data:rsvp-unresolved-async', count: 1 });
}

requestPromise.then(() => {
assert.ok(false, 'we should never make it here');
});
Expand Down
15 changes: 9 additions & 6 deletions packages/-ember-data/tests/unit/store/async-leak-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { run } from '@ember/runloop';
import Ember from 'ember';

import { module } from 'qunit';
import { Promise } from 'rsvp';
Expand All @@ -8,6 +7,7 @@ import { setupTest } from 'ember-qunit';

import JSONAPIAdapter from '@ember-data/adapter/json-api';
import Model, { attr } from '@ember-data/model';
import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';
import JSONAPISerializer from '@ember-data/serializer/json-api';
import Store from '@ember-data/store';
import test from '@ember-data/unpublished-test-infra/test-support/test-in-debug';
Expand Down Expand Up @@ -195,11 +195,6 @@ module('unit/store async-waiter and leak detection', function (hooks) {

assert.false(waiter(), 'We return false to keep waiting while requests are pending');

// needed for LTS 2.16
Ember.Test.adapter.exception = (e) => {
throw e;
};

assert.throws(() => {
run(() => store.destroy());
}, /Async Request leaks detected/);
Expand All @@ -210,6 +205,10 @@ module('unit/store async-waiter and leak detection', function (hooks) {
run(() => next());
assert.strictEqual(store._trackedAsyncRequests.length, 0, 'Our pending request is cleaned up');
assert.true(waiter(), 'We return true because the waiter is cleared');

if (DEPRECATE_RSVP_PROMISE) {
assert.expectDeprecation({ id: 'ember-data:rsvp-unresolved-async', count: 1 });
}
});

test('when the store is torn down too early, but the waiter behavior is turned off, we emit a warning', async function (assert) {
Expand Down Expand Up @@ -257,6 +256,10 @@ module('unit/store async-waiter and leak detection', function (hooks) {
run(() => next());
assert.strictEqual(store._trackedAsyncRequests.length, 0, 'Our pending request is cleaned up');
assert.true(waiter(), 'We return true because the waiter is cleared');

if (DEPRECATE_RSVP_PROMISE) {
assert.expectDeprecation({ id: 'ember-data:rsvp-unresolved-async', count: 1 });
}
});

test('when configured, pending requests have useful stack traces', async function (assert) {
Expand Down
1 change: 1 addition & 0 deletions packages/private-build-infra/addon/current-deprecations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
export default {
DEPRECATE_CATCH_ALL: '99.0',
DEPRECATE_3_12: '3.12',
DEPRECATE_RSVP_PROMISE: '4.4',
DEPRECATE_SAVE_PROMISE_ACCESS: '4.4',
};
1 change: 1 addition & 0 deletions packages/private-build-infra/addon/deprecations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ function deprecationState(deprecationName: keyof typeof CURRENT_DEPRECATIONS): b
export const DEPRECATE_CATCH_ALL = deprecationState('DEPRECATE_CATCH_ALL');
export const DEPRECATE_3_12 = deprecationState('DEPRECATE_3_12');
export const DEPRECATE_SAVE_PROMISE_ACCESS = deprecationState('DEPRECATE_SAVE_PROMISE_ACCESS');
export const DEPRECATE_RSVP_PROMISE = deprecationState('DEPRECATE_RSVP_PROMISE');
22 changes: 21 additions & 1 deletion packages/store/addon/-private/system/fetch-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
* @module @ember-data/store
*/
import { A } from '@ember/array';
import { assert, warn } from '@ember/debug';
import { assert, deprecate, warn } from '@ember/debug';
import { _backburner as emberBackburner } from '@ember/runloop';
import { DEBUG } from '@glimmer/env';

import { default as RSVP, Promise } from 'rsvp';

import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';

import type { CollectionResourceDocument, SingleResourceDocument } from '../ts-interfaces/ember-data-json-api';
import type { FindRecordQuery, Request, SaveRecordMutation } from '../ts-interfaces/fetch-manager';
import type { ExistingRecordIdentifier, RecordIdentifier, StableRecordIdentifier } from '../ts-interfaces/identifier';
Expand Down Expand Up @@ -139,6 +141,24 @@ export default class FetchManager {

promise = promise.then(
(adapterPayload) => {
if (!_objectIsAlive(internalModel)) {
if (DEPRECATE_RSVP_PROMISE) {
deprecate(
`A Promise while saving ${modelName} did not resolve by the time your model was destroyed. This will error in a future release.`,
false,
{
id: 'ember-data:rsvp-unresolved-async',
until: '5.0',
for: '@ember-data/store',
since: {
available: '4.5',
enabled: '4.5',
},
}
);
}
}

if (adapterPayload) {
return normalizeResponseHelper(serializer, store, modelClass, adapterPayload, snapshot.id, operation);
}
Expand Down
25 changes: 24 additions & 1 deletion packages/store/addon/-private/system/store/common.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { deprecate } from '@ember/debug';
import { get } from '@ember/object';
import { DEBUG } from '@glimmer/env';

import { resolve } from 'rsvp';

import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';

/**
@module @ember-data/store
*/
Expand Down Expand Up @@ -32,7 +35,27 @@ export function guardDestroyedStore(promise, store, label) {
if (DEBUG) {
token = store._trackAsyncRequestStart(label);
}
let wrapperPromise = resolve(promise, label).then((v) => promise);
let wrapperPromise = resolve(promise, label).then((_v) => {
if (!_objectIsAlive(store)) {
if (DEPRECATE_RSVP_PROMISE) {
deprecate(
`A Promise did not resolve by the time the store was destroyed. This will error in a future release.`,
false,
{
id: 'ember-data:rsvp-unresolved-async',
until: '5.0',
for: '@ember-data/store',
since: {
available: '4.5',
enabled: '4.5',
},
}
);
}
}

return promise;
});

return _guard(wrapperPromise, () => {
if (DEBUG) {
Expand Down
58 changes: 53 additions & 5 deletions packages/store/addon/-private/system/store/finders.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { assert } from '@ember/debug';
import { assert, deprecate } from '@ember/debug';
import { DEBUG } from '@glimmer/env';

import { Promise } from 'rsvp';

import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';

import { _bind, _guard, _objectIsAlive, guardDestroyedStore } from './common';
import { normalizeResponseHelper } from './serializer-response';

Expand Down Expand Up @@ -212,10 +214,26 @@ export function _findHasMany(adapter, store, internalModel, link, relationship,
let label = `DS: Handle Adapter#findHasMany of '${internalModel.modelName}' : '${relationship.type}'`;

promise = guardDestroyedStore(promise, store, label);
promise = _guard(promise, _bind(_objectIsAlive, internalModel));

return promise.then(
promise = promise.then(
(adapterPayload) => {
if (!_objectIsAlive(internalModel)) {
if (DEPRECATE_RSVP_PROMISE) {
deprecate(
`A Promise for fecthing ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`,
false,
{
id: 'ember-data:rsvp-unresolved-async',
until: '5.0',
for: '@ember-data/store',
since: {
available: '4.5',
enabled: '4.5',
},
}
);
}
}

assert(
`You made a 'findHasMany' request for a ${internalModel.modelName}'s '${relationship.key}' relationship, using link '${link}' , but the adapter's response did not have any data`,
payloadIsNotBlank(adapterPayload)
Expand All @@ -236,6 +254,12 @@ export function _findHasMany(adapter, store, internalModel, link, relationship,
null,
`DS: Extract payload of '${internalModel.modelName}' : hasMany '${relationship.type}'`
);

if (DEPRECATE_RSVP_PROMISE) {
promise = _guard(promise, _bind(_objectIsAlive, internalModel));
}

return promise;
}

export function _findBelongsTo(adapter, store, internalModel, link, relationship, options) {
Expand All @@ -249,8 +273,26 @@ export function _findBelongsTo(adapter, store, internalModel, link, relationship
promise = guardDestroyedStore(promise, store, label);
promise = _guard(promise, _bind(_objectIsAlive, internalModel));

return promise.then(
promise = promise.then(
(adapterPayload) => {
if (!_objectIsAlive(internalModel)) {
if (DEPRECATE_RSVP_PROMISE) {
deprecate(
`A Promise for fetching ${relationship.type} did not resolve by the time your model was destroyed. This will error in a future release.`,
false,
{
id: 'ember-data:rsvp-unresolved-async',
until: '5.0',
for: '@ember-data/store',
since: {
available: '4.5',
enabled: '4.5',
},
}
);
}
}

let serializer = store.serializerFor(relationship.type);
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');

Expand All @@ -271,6 +313,12 @@ export function _findBelongsTo(adapter, store, internalModel, link, relationship
null,
`DS: Extract payload of ${internalModel.modelName} : ${relationship.type}`
);

if (DEPRECATE_RSVP_PROMISE) {
promise = _guard(promise, _bind(_objectIsAlive, internalModel));
}

return promise;
}

export function _findAll(adapter, store, modelName, options) {
Expand Down