SetValue does not have effect immediately?

I have this simple CustomWidget.
Why do I see unexpected values for N even though setValue was called just before getValue?

HTML

<div id="mainblock">
  <div id="log_display">NO LOG YET</div>
</div>

JavaScript

"use strict";
let log = [];
function add_log(str) {
  log.push(`[${new Date(Date.now()).toLocaleTimeString()}]${str}`);
  document.querySelector("#log_display").innerText = log.join("\n");
}
getValue("B", (value) => {
  if (value == true) {
    setValue("N", 0);
    add_log(`expecting 0 -> N=${getValue("N")}`);
    setValue("N", 1);
    add_log(`expecting 1 -> N=${getValue("N")}`);
    setValue("N", 2);
    add_log(`expecting 2 -> N=${getValue("N")}`);
  }
});

CSS

#log_display {
  border: solid;
  font-size: 2em;
}

Because of this behavior, we cannot rely on this line 7 in LOOPER v2.1 to have the “Iteration” initialized to 0.
We need make sure the prop “Iteration” is initialized to 0 in the app side before changing the “Begin Loop” prop to Yes.

Hi Ta-Aoki,

Thanks for reaching out!

The behavior you’re seeing is expected: getValue() is asynchronous when used with a callback, so calling it directly like getValue(“N”) inside your add_log() statements will likely return undefined, as the value hasn’t yet been set or retrieved at that point.

To better assist you, could you please share what you’re trying to achieve with this sequence of setting and logging values? Understanding the goal will help us suggest an appropriate solution or workaround.

Looking forward to your reply!

Best regards,
Misi

Hi Ta‑Aoki,

Thank you for pointing this out—you’re absolutely right that in Looper v2.1 the app must set Iteration = 0 before toggling Begin Loop = Yes, otherwise the first loop may start with an undefined value.

The good news is that Looper v3 (rolling out tomorrow, 19 June 2025) takes care of this for you:

  • Automatic initialization – When Begin Loop transitions to Yes, Looper v3 now resets Iteration to 0 behind the scenes, so no extra step is required in your app.
  • Start from Index – If you need to begin the loop somewhere other than the first item, the new Start from Index input lets you specify the initial position explicitly. Setting this to, say, 5 will start the first pass with Iteration = 5.

Once the update is live, you can remove any manual initialization you were doing and, if needed, use Start from Index to “jump” into the list.

Let us know if this addresses your use case—or if there are other edge cases you’d like us to cover!

Best regards,
Misi

Thank you for the explanation.

However, I still see the same unexpected results, even if take getValue() outside the callback.
Is this expected behavior?

could you please share what you’re trying to achieve with this sequence of setting and logging values?

I am just trying to understand the true behavior of setValue() and getValue() so that I can use it correctly inside my widgets.
Please let me know if it is documented somewhere.

I am extremely interested in Looper v3. I will definitely check it out!

Hi Ta-Aoki,

Happy to dig a little deeper into what’s happening under the hood.
The key point is that setValue() and getValue() are not local, in‑memory calls inside the same JavaScript context. Your custom widget is running in its own <iframe>, while the Tulip app lives in the parent page. The two exchange data through postMessage, so every read or write is an asynchronous round‑trip across that iframe boundary.

What actually happens

  1. setValue("N", x)
  • The widget posts a message to the parent frame saying “please store N = x”.
  • The call returns immediately—no guarantee the parent has processed it yet.
  1. Parent frame
  • Receives the message, updates its internal value store, and then sends a confirmation message back to the widget.
  1. Widget
  • When the confirmation arrives, the widget’s local cache is updated.
  • Until this message arrives, a subsequent getValue("N") will still return the previous value (or undefined).

Because network‑style messaging is involved, even though it’s all happening on the same machine the whole trip still takes at least one event‑loop tick.

Minimal delay example

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function runProcesses() {
  setValue("N", 5);

  await delay(100); // waiting 0,1s

  let pN = getValue("N");

  console.log(pN);
}

runProcesses();

A setTimeout(..., 0) (or await Promise.resolve()) simply defers execution to the next event‑loop cycle, which is enough time for the parent frame to process the message and reply.

Tip: In most real‑world flows you don’t need to fetch the value right after you set it—the app already “knows” the value you just sent. If you’re only logging for debugging, the delay trick above is perfect; if you need to react to the change inside your widget, consider subscribing to the widget’s value‑change event instead.

Hope this clarifies why getValue() was showing undefined and how to work around it. Let me know if you’d like more detail or code samples!

Best,
Misi

1 Like

Thank you very much for the detailed explanation!

I confirm that waiting 100msec did make the script behave expectedly. :grinning_face_with_smiling_eyes:(at least when I tested)