Skip to content

Commit ad314e0

Browse files
committedFeb 26, 2025·
fix(core): broken promise resolving for proxy objects
1 parent 688e4d7 commit ad314e0

File tree

3 files changed

+36
-17
lines changed

3 files changed

+36
-17
lines changed
 
+27-16
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,46 @@
11
import { defineHeadPlugin } from './defineHeadPlugin'
22

3-
async function resolvePromisesRecursively(root: any): Promise<any> {
4-
if (root instanceof Promise) {
5-
return await root
3+
async function walkPromises(v: any): Promise<any> {
4+
const type = typeof v
5+
if (type === 'function') {
6+
return v
67
}
7-
// could be a root primitive, array or object
8-
if (Array.isArray(root)) {
9-
return Promise.all(root.map(r => resolvePromisesRecursively(r)))
8+
// Combined primitive type check
9+
if (v instanceof Promise) {
10+
return await v
1011
}
11-
if (typeof root === 'object') {
12-
const resolved: Record<string, string> = {}
1312

14-
for (const k in root) {
15-
if (!Object.hasOwn(root, k))
16-
continue
13+
if (Array.isArray(v)) {
14+
return await Promise.all(v.map(r => walkPromises(r)))
15+
}
1716

18-
resolved[k] = await resolvePromisesRecursively(root[k])
17+
if (v?.constructor === Object) {
18+
const next: Record<string, any> = {}
19+
for (const key of Object.keys(v)) {
20+
next[key] = await walkPromises(v[key])
1921
}
20-
21-
return resolved
22+
return next
2223
}
23-
return root
24+
25+
return v
2426
}
2527

2628
export const PromisesPlugin = /* @__PURE__ */ defineHeadPlugin({
2729
key: 'promises',
2830
hooks: {
2931
'entries:resolve': async (ctx) => {
32+
const promises = []
3033
for (const k in ctx.entries) {
31-
ctx.entries[k].input = await resolvePromisesRecursively(ctx.entries[k].input)
34+
if (!ctx.entries[k]._promisesProcessed) {
35+
promises.push(
36+
walkPromises(ctx.entries[k].input).then((val) => {
37+
ctx.entries[k].input = val
38+
ctx.entries[k]._promisesProcessed = true
39+
}),
40+
)
41+
}
3242
}
43+
await Promise.all(promises)
3344
},
3445
},
3546
})

‎packages/unhead/src/types/head.ts

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ export interface HeadEntry<Input> {
6666
* @internal
6767
*/
6868
_tags?: HeadTag[]
69+
/**
70+
* @internal Remove once promise plugin is removed
71+
*/
72+
_promisesProcessed?: boolean
6973
}
7074

7175
export type HeadPluginOptions = Omit<CreateHeadOptions, 'plugins'>

‎packages/vue/test/unit/promises.test.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, it } from 'vitest'
2+
import { computed } from 'vue'
23
import { useHead } from '../../src'
34
import { PromisesPlugin } from '../../src/plugins'
45
import { ssrVueAppWithUnhead } from '../util'
@@ -12,6 +13,7 @@ describe('vue promises', () => {
1213
{ src: new Promise(resolve => resolve('https://example.com/script.js')) },
1314
{
1415
innerHTML: new Promise<string>(resolve => setTimeout(() => resolve('test'), 250)),
16+
foo: computed(() => 'test'),
1517
},
1618
],
1719
})
@@ -43,7 +45,9 @@ describe('vue promises', () => {
4345
"_p": 1026,
4446
"_w": 100,
4547
"innerHTML": "test",
46-
"props": {},
48+
"props": {
49+
"foo": "test",
50+
},
4751
"tag": "script",
4852
},
4953
]

0 commit comments

Comments
 (0)
Please sign in to comment.