Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

2.0.0-rc.18 | `await refresh(memo)` hangs in an action after an optimistic write and a dependency change

オープン
#4,263 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@brenelz がすでに取り組んでいます。

2026年10月25日 から。

  • #4267 @brenelz による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
57/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
javascript, node.js, typescript
領域
backend, testing

調査の方向性

Start with the reproduction in the issue body and the @solidjs/signals implementation of action, createOptimistic, and refresh; the issue reports that the behavior also reproduces on the next branch. Run the supplied Node.js reproduction and compare it with the listed variations to narrow down the interaction. Done means refresh(m) resolves, the action completes, and likes() returns 2 in the hanging case.

索引モデルが issue の本文から書いたものです。

説明

Describe the bug

In an action, await refresh(memo) does not resolve when all of the following happen:

  1. The action writes an optimistic value derived from an async memo.
  2. A dependency of the memo changes during the action, and the memo re-runs and returns a new value (a different object).
  3. After that re-run has settled, refresh(memo) is awaited inside an async function that the action yields.

The action's promise does not resolve either (still pending after 10 seconds in my tests), and likes() keeps returning the optimistic value 3, although the memo takes 5 ms.

Your Example Website or App

https://stackblitz.com/edit/vzxgvake?file=index.js

It runs npm start on open and prints timeout likes = 3.

Steps to Reproduce the Bug or Issue

Run this as an ES module with Node.js:

import {
  action,
  createMemo,
  createOptimistic,
  createSignal,
  flush,
  refresh,
} from '@solidjs/signals';

const tick = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const [s, setS] = createSignal(0);
// Returns a new object on every run, so each re-run produces a new value.
const m = createMemo(async () => {
  s();
  await tick(5);
  return { likes: 2 };
});
const [likes, setLikes] = createOptimistic(() => m().likes);
flush();
await tick(50);

const run = action(function* () {
  setLikes(likes() + 1); // (1) optimistic write
  yield (async () => {
    await tick(5);
    setS(1); // (2) dependency of `m` changes, so `m` re-runs
    await tick(30); // (3) wait until the re-run settles
    await refresh(m); // does not resolve
  })();
});

const result = await Promise.race([
  run().then(() => 'resolved'),
  tick(2000).then(() => 'timeout'),
]);
console.log(result, 'likes =', likes());

Output:

timeout likes = 3

The table shows the result of each change to the reproduction. The results are the same on rc.15, rc.18, and the current next branch.

Change Result
Remove the optimistic write (1) resolved, likes = 2
Derive the optimistic value from a constant instead of m (createOptimistic(() => 2)) resolved, likes = 2
Remove setS(1) (2) resolved, likes = 2
Wait 1 ms instead of 30 ms at (3), so refresh starts while the re-run is still pending resolved, likes = 2
The memo returns 2 instead of { likes: 2 } (and createOptimistic(() => m())), so the re-run returns an equal value resolved, likes = 2
Yield refresh(m) directly from the generator (code below) resolved, likes = 2
Call setS(1) in a generator step and keep await refresh(m) inside the yielded async function (code below) timeout, likes = 3

Yielding refresh(m) directly from the generator, which resolves:

const run = action(function* () {
  setLikes(likes() + 1);
  yield (async () => {
    await tick(5);
    setS(1);
    await tick(30);
  })();
  yield refresh(m);
});

Calling setS(1) in a generator step, which still hangs:

const run = action(function* () {
  setLikes(likes() + 1);
  yield tick(5);
  setS(1);
  yield (async () => {
    await tick(30);
    await refresh(m);
  })();
});

Expected behavior

await refresh(m) resolves after the memo re-runs, run() resolves, and likes() returns 2.

The documentation of refresh in dist/types/signals.d.ts says that it returns "a promise for the target's NEXT QUIESCENT STATE" and that "Accessor targets resolve with the settled value". In the hanging case, the memo has run three times (initial run, re-run from setS, re-run from refresh) and m() returns { likes: 2 }, but the promise does not resolve.

Screenshots or Videos

None.

Platform

  • OS: Windows 11
  • Runtime: Node.js 24.16.0 (no browser involved)
  • Version: @solidjs/signals 2.0.0-rc.15 and 2.0.0-rc.18

It also reproduces on the next branch at 4ea2f49 (2026-10-25), with dist/dev.js and dist/prod built by rollup -c in packages/signals. Every row of the table above gives the same result there.

Additional context

Workaround: yield refresh(m) from the generator instead of awaiting it inside a yielded async function.

I found this in a framework that passes component props through signals. A parent re-render during a user action can set a prop to a new object. Then a memo that reads the prop returns a new value, and the action, which wrote an optimistic value and then awaits refresh, hangs.

主要言語
TypeScript
スター
36.2k
フォーク
1.1k
平均マージ
11時間 19分
マージ済み PR(30日)
337

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

solidjs/solid のほかの issue

solidjs/solid の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。