使用@urql/svelte时响应式语句调用query触发初始化外调用错误求助
我来帮你拆解下这个问题的核心原因,然后给出几个可行的解决方案:
为什么会触发这个错误?
这个Error: Function called outside component initialization的根源在于Svelte上下文API的执行限制:getContext(也就是@urql/svelte里getClient依赖的方法)只能在组件初始化的同步代码中,或者组件生命周期钩子(比如onMount)的调用栈内执行。
你在响应式语句的异步函数里加了await sleep(2000),这行代码会让后续的代码执行脱离组件的上下文作用域——等2秒后再调用query方法时,query内部调用的getClient()已经找不到组件上下文了,自然就报错了。
而移除await sleep时,query是在响应式语句的同步执行阶段调用的,此时组件上下文还在;放到onMount里时,哪怕是异步代码,也依然处于组件的上下文栈内,所以能正常工作。
可行的解决方案
方案1:提前获取URQL客户端,异步代码直接调用客户端方法
在组件初始化的同步阶段先拿到URQL客户端实例,之后在异步代码里用客户端自带的query方法发起请求,避开依赖组件上下文的@urql/svelte的query函数:
<script> import { getClient } from '@urql/svelte' import { myQuery } from './myQuery' let players let myVars // 组件初始化时同步获取客户端,此时上下文有效 const client = getClient(); function sleep (ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } $: (async () => { await sleep(2000) // 用客户端的query方法发起请求,无需依赖组件上下文 const result = await client.query({ query: myQuery, variables: { ...myVars }, requestPolicy: 'cache-and-network' }).toPromise(); players = result.data; })() </script> {#if !players} Loading players... {:else} Players loaded! Do the work. {/if}
方案2:把延迟逻辑移到onMount(适合无需响应变量变化的场景)
如果你的延迟请求不需要跟着myVars的变化触发,直接把逻辑放到onMount里是最省心的:
<script> import { onMount } from 'svelte' import { query } from '@urql/svelte' import { myQuery } from './myQuery' let players let myVars function sleep (ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } onMount(async () => { await sleep(2000) players = query({ query: myQuery, variables: { ...myVars }, requestPolicy: 'cache-and-network' }) }) </script> {#if !players} Loading players... {:else} Players loaded! Do the work. {/if}
方案3:响应变量变化+延迟请求的组合写法
如果需要在myVars变化时触发延迟请求,保持响应式特性的同时避免上下文问题:
<script> import { getClient } from '@urql/svelte' import { myQuery } from './myQuery' let players let myVars const client = getClient(); function sleep (ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } // 仅当myVars存在/变化时,触发延迟请求 $: if (myVars) { (async () => { await sleep(2000) const result = await client.query({ query: myQuery, variables: { ...myVars }, requestPolicy: 'cache-and-network' }).toPromise(); players = result.data; })(); } </script> {#if !players} Loading players... {:else} Players loaded! Do the work. {/if}
补充说明
从你贴的@urql/svelte的context.ts代码能看出来,query方法内部肯定会调用getClient()(也就是getContext(CLIENT)),而getContext必须在组件的生命周期上下文里执行。异步代码里的await会打断这个上下文链,导致后续调用失败,这就是问题的本质啦。
内容的提问来源于stack exchange,提问作者Fred Hors

