AWS AppSync查询即使禁用离线功能也会返回缓存响应

7
我有一个相当简单的Node应用程序,使用AWS AppSync。我能够成功运行查询和变异,但最近发现,如果我运行两次查询,即使我知道后端数据已更改,我也会得到相同的响应。在这种特定情况下,查询由lambda支持,在挖掘它时,我发现每次查询运行时查询似乎没有发送到网络上,因为lambda仅在第一次运行时触发。如果我使用控制台模拟我的查询,那么一切都正常运行。如果我重新启动我的应用程序,那么第一次查询运行正常,但是连续的查询每次都返回相同的值。
以下是我的部分代码:
  client.query({
    query: gql`
    query GetAbc($cId: String!) {
      getAbc(cId: $cId) {
        id
        name
        cs
      }
    }`,
    options: {
      fetchPolicy: 'no-cache'
    },
    variables: {
      cid: event.cid
    }
  })
    .then((data) => {
      // same data every time
    })

编辑: 尝试其他的获取策略,如network-only没有明显的区别。

这是我设置客户端的方式,不是很规范但似乎可行:

const makeAWSAppSyncClient = (credentials) => {
  return Promise.resolve(
    new AWSAppSyncClient({
      url: 'lalala',
      region: 'us-west-2',
      auth: {
        type: 'AWS_IAM',
        credentials: () => {
          return credentials
        }
      },
      disableOffline: true
    })
  )
}

getRemoteCredentials()
  .then((credentials) => {
    return makeAWSAppSyncClient(credentials)
  })
  .then((client) => {
    return client.hydrated()
  })
  .then((client) => {
    // client is good to use
  })

getRemoteCredentials是一种将IoT身份验证转换为可与其他AWS SDK一起使用的正常IAM凭据的方法。这是有效的(否则我不会走得这么远)。

我的问题似乎与此问题非常相似GraphQL Query Runs Sucessfully One Time and Fails To Run Again using Apollo and AWS AppSync; 我正在使用Node环境(而不是React),但本质上是相同的问题。


我认为这并不重要,但为了完整起见,我应该提到我已经尝试过有和没有文档中的设置代码。这似乎没有任何区别(除了令人讨厌的日志记录,见下文),但在这里它是:

global.WebSocket = require('ws')
global.window = global.window || {
  setTimeout: setTimeout,
  clearTimeout: clearTimeout,
  WebSocket: global.WebSocket,
  ArrayBuffer: global.ArrayBuffer,
  addEventListener: function () { },
  navigator: { onLine: true }
}

global.localStorage = {
  store: {},
  getItem: function (key) {
    return this.store[key]
  },
  setItem: function (key, value) {
    this.store[key] = value
  },
  removeItem: function (key) {
    delete this.store[key]
  }
};
require('es6-promise').polyfill()
require('isomorphic-fetch')

这是从以下网址获取的:https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-javascript.html 使用此代码并在客户端设置中没有offlineDisabled: true,我在控制台上连续看到这一行输出:
redux-persist asyncLocalStorage需要全局localStorage对象。 要么使用不同的存储后端,要么如果这是一个通用的redux应用程序,则应该有条件地进行持久化,如下所示: https://gist.github.com/rt2zz/ac9eb396793f95ff3c3b 然而,这对此问题似乎没有明显的影响。
更新:我的package.json依赖项,我在测试期间已经升级了这些依赖项,因此我的yarn.lock包含比此处列出的更近期的修订版。尽管如此:https://gist.github.com/macbutch/a319a2a7059adc3f68b9f9627598a8ca 更新#2:我还从CloudWatch日志中确认查询仅被运行一次;我有一个定时器上定期运行的突变,成功调用并在CloudWatch中可见。这正如我所期望的那样工作,但查询没有。
更新#3:我已经对AppSync / Apollo代码进行了调试,并可以看到我的fetchPolicy在apollo-client/core/QueryManager.js中更改为'cache-first'(我的评论)。
QueryManager.prototype.fetchQuery = function (queryId, options, fetchType, fetchMoreForQueryId) {
    var _this = this;
    // Next line changes options.fetchPolicy to 'cache-first'
    var _a = options.variables, variables = _a === void 0 ? {} : _a, _b = options.metadata, metadata = _b === void 0 ? null : _b, _c = options.fetchPolicy, fetchPolicy = _c === void 0 ? 'cache-first' : _c;
    var cache = this.dataStore.getCache();
    var query = cache.transformDocument(options.query);
    var storeResult;
    var needToFetch = fetchPolicy === 'network-only' || fetchPolicy === 'no-cache';
    // needToFetch is false (because fetchPolicy is 'cache-first')
    if (fetchType !== FetchType.refetch &&
        fetchPolicy !== 'network-only' &&
        fetchPolicy !== 'no-cache') {
        // so we come through this branch
        var _d = this.dataStore.getCache().diff({
            query: query,
            variables: variables,
            returnPartialData: true,
            optimistic: false,
        }), complete = _d.complete, result = _d.result;
        // here complete is true, result is from the cache
        needToFetch = !complete || fetchPolicy === 'cache-and-network';
        // needToFetch is still false
        storeResult = result;
    }
    // skipping some stuff
    ...
    if (shouldFetch) { // shouldFetch is still false so this doesn't execute
        var networkResult = this.fetchRequest({
            requestId: requestId,
            queryId: queryId,
            document: query,
            options: options,
            fetchMoreForQueryId: fetchMoreForQueryId,
    }
    // resolve with data from cache
    return Promise.resolve({ data: storeResult });

如果我使用调试器将shouldFetch的值更改为true,那么至少我会看到一个网络请求被发送并且我的lambda函数执行。我猜我需要解开改变我的fetchPolicy的那行代码在做什么。


你为什么要用一个带有几个方法的对象来覆盖本地存储? - Joe Warner
我正在运行在一个节点环境中(而不是React):这是否意味着您无法访问窗口对象? - Joe Warner
无论我做还是不做都没有区别。这在AWS的示例中已经存在,所以我添加了那个片段来展示我已经尝试过了,但它实际上是从文档中复制粘贴的。 - macbutch
3个回答

16

好的,我找到了问题。这是我问题中代码的摘要:

 client.query({
    query: gql`...`,
    options: {
      fetchPolicy: 'no-cache'
    },
    variables: { ... }
  })

现在要解决的问题变得稍微简单一些了。这是正确的写法:

 client.query({
    query: gql`...`,
    fetchPolicy: 'network-only'
    variables: { ... }
  })

我原来的问题有两个:

  1. fetchPolicy: 'no-cache' 在这里似乎不起作用(我得到一个空响应)
  2. fetchPolicy 放在 options 对象中是不必要的

graphql 客户端以不同的方式指定选项,我们在这两者之间切换。


3

在AWS Lambda函数中运行时,将查询fetch-policy设置为'network-only'

我建议使用WebSocketwindowlocalStorage的覆盖,因为这些对象在Lambda函数中并不适用。我通常在Lambda中使用以下设置来运行NodeJS应用程序。

'use strict';

// CONFIG
const AppSync = {
    "graphqlEndpoint": "...",
    "region": "...",
    "authenticationType": "...",
    // auth-specific keys
};

// POLYFILLS
global.WebSocket = require('ws');
global.window = global.window || {
    setTimeout: setTimeout,
    clearTimeout: clearTimeout,
    WebSocket: global.WebSocket,
    ArrayBuffer: global.ArrayBuffer,
    addEventListener: function () { },
    navigator: { onLine: true }
};
global.localStorage = {
    store: {},
    getItem: function (key) {
        return this.store[key]
    },
    setItem: function (key, value) {
        this.store[key] = value
    },
    removeItem: function (key) {
        delete this.store[key]
    }
};
require('es6-promise').polyfill();
require('isomorphic-fetch');

// Require AppSync module
const AUTH_TYPE = require('aws-appsync/lib/link/auth-link').AUTH_TYPE;
const AWSAppSyncClient = require('aws-appsync').default;

// INIT
// Set up AppSync client
const client = new AWSAppSyncClient({
    url: AppSync.graphqlEndpoint,
    region: AppSync.region,
    auth: {
        type: AppSync.authenticationType,
        apiKey: AppSync.apiKey
    }
});

如果你继续遇到问题,能否将你的package.json中依赖项和版本列表粘贴出来? - Rohan Deshpande
关于依赖性的观点很好。我会更新以包括它。 - macbutch
是的,好的,我已经有这么多工作了。我的问题是,无论我运行查询多少次,我的代码都会接收到第一次调用的结果(无论后端数据是否更改)。实际上,AppSync应该调用的Lambda只被调用一次。 - macbutch
谢谢@Rohan。我刚刚发布了一个可行的答案。你说的关于使用“network-only”的方法是正确的,但另一个问题是使用了嵌套的选项对象(就像我们为GraphQL客户端所做的那样,在这里是错误的)。 - macbutch
@macbutch 太棒了 - 很高兴听到现在它正在工作。我会更新AppSync文档以指出这些陷阱。感谢您的反馈! - Rohan Deshpande
显示剩余3条评论

2
有两种选项可以使用AppSyncClient/ApolloClient启用/禁用缓存,一种是针对每个查询的,另一种是在初始化客户端时设置。
客户端配置:
client = new AWSAppSyncClient(
  {
    url: 'https://myurl/graphql',
    region: 'my-aws-region',
    auth: {
      type: AUTH_TYPE.AWS_MY_AUTH_TYPE,
      credentials: await getMyAWSCredentialsOrToken()
    },
    disableOffline: true
  },
  {
    cache: new InMemoryCache(),
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'no-cache', // <-- HERE: check the apollo fetch policy options
        errorPolicy: 'ignore'
      },
      query: {
        fetchPolicy: 'no-cache',
        errorPolicy: 'all'
      }
    }
  }
);

备选项:查询选项:
export default graphql(gql`query { ... }`, {
    options: { fetchPolicy: 'cache-and-network' },
})(MyComponent);

有效的 fetchPolicy 值包括:
  • cache-first(缓存优先):这是默认值,我们总是首先尝试从缓存中读取数据。如果满足查询所需的所有数据都在缓存中,则返回该数据。只有当缓存中没有可用的结果时,Apollo才会从网络获取数据。此获取策略旨在在渲染组件时最小化发送的网络请求数量。
  • cache-and-network(缓存和网络):此获取策略将使Apollo首先尝试从缓存中读取数据。如果满足查询所需的所有数据都在缓存中,则返回该数据。但是,无论是否在缓存中存在完整数据,此fetchPolicy始终会使用网络接口执行查询,而不像“cache-first”仅在查询数据不在缓存中时才执行查询。此获取策略旨在在用户快速响应的同时,尽可能保持缓存数据与服务器数据一致,但代价是需要额外的网络请求。
  • network-only(仅网络):此获取策略永远不会从缓存中返回初始数据。相反,它将始终使用您的网络接口向服务器发出请求。此获取策略旨在实现与服务器的数据一致性,但代价是当可用时,无法立即响应用户。
  • cache-only(仅缓存):此获取策略永远不会使用您的网络接口执行查询。相反,它将始终尝试从缓存中读取数据。如果查询的数据不存在于缓存中,则会抛出错误。此获取策略允许您仅与本地客户端缓存中的数据交互,而不进行任何网络请求,这可以使您的组件快速响应,但意味着您的本地数据可能与服务器上的数据不一致。如果您只想与Apollo Client缓存中的数据交互,请确保查看可用于ApolloClient实例的readQuery()readFragment()方法。
  • no-cache(无缓存):此获取策略永远不会从缓存中返回初始数据。相反,它将始终使用您的网络接口向服务器发出请求。与“network-only”策略不同,它在查询完成后也不会将任何数据写入缓存中。
复制自:https://www.apollographql.com/docs/react/api/react-hoc/#graphql-options-for-queries

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接