flow.until()
Stop a flow using the flow.until() method using either a condition on the trigger, a promise, or another flow.
Syntax
flow.until(callback);
flow.until(thenable);
Parameters
- callback
- A callback to add onto the callback pipeline. Like
.then()
, it receives the trigger arguments as parameters. If the callback returns something truthy, the flow is stopped immediately and the trigger does not continue through the rest of the pipeline.
- thenable
- A thenable, such as another
Flow
object, a Promise, or any other object with a .then() method. The flow is stopped whenever this thenable triggers or resolves. When passing a Flow then it is automatically stopped whenever the original flow stops; in other words, calling .once()
on the passed flow is not necessary.
Return value
The same Flow
object the method was called on, allowing for method chaining.
Examples
Drag and drop
Let's create a simple drag-and-drop system. We'll have an element on the page we'll be dragging around, maintaining its position using fixed positioning with dynamic top and left properties. We'll be using when()
to attach a pointerdown event to the draggable element. In the handler, we attach a pointermove event to the document, keeping track of its position, until we see a pointerup event. Our code reads much like that:
const element = document.querySelector('#element');
let left = 0;
let top = 0;
when(element).pointerdowns().then(() => {
when(document).pointermoves().then(event => {
left += event.movementX;
top += event.movementY;
element.style.left = `${left}px`;
element.style.top = `${top}px`;
}).until(when(document).pointerups())
});
The .until() method makes sure the pointermove handler is disconnected. This is great for performance, since now the only event handler that exists while the user is not dragging is a single pointerdown listener.
Animations
Let's try to build a little on the previous example, but instead of dragging the element, we'll be moving it along a horizontal line using a JavaScript animation. This animation will take two seconds and it'll move the element over 500 pixels. To achieve this, we use frame()
to run the code every frame, and .until() to make sure the animation stops when the two seconds run out. Note that frame() receives one trigger argument, a DOMHighResTimeStamp for the current time, akin to document.timeline.currentTime. We'll use this to calculate the offset and stop the animation at the right moment.
const element = document.querySelector('#element');
const start = document.timeline.currentTime;
const duration = 2000;
const distance = 500;
frame().then(timestamp => {
const left = (timestamp - start) / duration * distance;
element.style.left = `${left}px`;
}).until(timestamp => timestamp - start > duration);
Here, instead of relying on another flow to trigger in order to stop the frame() animation, we return a boolean depending on the timestamp argument indicating whether it is time to stop. Note that we could theoretically write the whole animation inside the .until(); this is not recommended, since it might not be obvious that the .until() handler has side effects.
Usage notes
When .until() stops the flow in question, no triggers are continued through the callback pipeline and the pipeline is stopped right away. This means that, when using .until() with a callback, and it is followed up with a .then() handler (like .until(…).then(…)), then the .then() handler is not run when the callback passed to .until() returns something truthy. In that regard, it functions somewhat like .if()
.
See also