---
# System prepended metadata

title: Modeling Asynchronous Data with Signals

---

# Modeling Asynchronous Data with Signals - Part One

What does it mean to have *asynchronous* signals?

Today's Signals have no concept of time; that's essentially a defining property. They're only for modeling observable state. Tools for async programming like Promise and RxJS are more for modeling *control flow*. These are fundamentally different concerns, but they intersect to form the foundation of how we build user interfaces, so we're highly motivated to find a cohesive answer.

So when we talk about async with signals, we're really asking what patterns are available for modeling these async data pipelines.

At first glance, it seems like RxJS is a better model for this behavior. It's worth saying that we must be willing to question whether Signals are even a good fit if that's where the evidence takes us and to not force them out of sheer ethusiasm.

In this article, I want to walk you through my thought process on subject and by the end share some conclusions, ideas, and open questions.

Let's begin by looking at some motivating examples -

## Example #1: Data Fetching

Here's how you might think to fetch a user's friends list using signals today. I'm sure this pattern is already proliferating in Angular.

```tsx!
function Foo(props: { user: Signal<User> }) {
    const friends = signal([]);

    effect(async () => {
        const results = await fetch(`users/friends?q=${user().id()}`);
        friends.set(results.json().friends);
    });
}
```

There's nothing terribly wrong with this example, but there is room for improvement. For one, it's quite imperative, which makes it hard to scale, compose, or otherwise reuse this pattern for more complex use cases. But where does it become a real problem?

Let's say we want to move this logic into a service. Something like:

```ts!
class UserService {
    user = signal({});
    friends = signal([]);
    
    constructor() {
        effect(() => {
            fetch(`users/friends?q=${user().id()}`)
                .then(res => res.json().friends)
                .then(this.user.set); // Nit: Breaks in Angular signals
        })
    }
}
```

### The Problem with Effects

This effect will always run (eagerly), making network requests, regardless of whether anybody is using the `friends` signal. We've gone through a lot of trouble to make `computeds` lazy, performant, and memoized, so it would be a shame to lose these properties at every async data boundary.

This is when you might turn to RxJS for the `switchMap` operation.

It turns out we can indeed model this using `computed` instead of `effect`, making it so the fetch actions will only occur when there are active effects listening.

If you previously thought of signal computations as being pure derivation functions, you'll first have to let go of this comforting idea. But this is actually a good thing. The computations must perform IO, and we can rely on the memoization properties to only perform those actions when necessary. This makes the memoized property of computeds much more important than an arbitrary performance optimization - it defines the semantics of when potentially mutating actions run.

However, we can keep this under control by saying the computations only **describe** the impure actions rather than directly carry them out - just like Promise.

### Modeling Async Actions in Computed

I know what you're thinking - what about initial values and error handling? One step at a time. For now, let's use `null` as the initial value and ignore error handling and see how far we get.

#### Lazy Actions with Computed

Now let's take a crack at swapping the effect with computed to get the lazy behavior. This is totally possible, though it might not be obvious. 

Try to solve this puzzle yourself before looking at the answer below -

```ts!
class UserService {
    public user = signal({});

    private request = computed(() => {
        const responseSig = signal(null);
        fetch('...')
            .then(res => res.json())
            .then(response.set);
        return responseSig;
    });

    public friends = () => {
        // Subscribe to both the request and its response data
        return this.request()();
    };
}
```

Ah, higher-order signals. What's happening here?

 * The `request` computed maps the `user` signal into a new `request` signal, which is a signal that directly models the promise.
 * The second computed subscribes to both the creation of the signal (`request`) and the value of the signal `responseSig`.

It's critical that the `request` signal here is a computed so that we aren't triggering a new fetch every time we pull the value of `friends`.

This is actually a very well known compositional pattern. It's called `flatMap` or `bind`. In RxJS, it's the `switchMap` operator. You basically produce (*map*) a wrapper inside of another wrapper, and then squish (*flatten*) them back together.

Anyways, now that we have something working, let's clean it up a bit by pulling out the reusable helpers.

```ts!
function fromPromise<T>(p: Promise<T>): Signal<null|T> {
  const s = signal<T|null>(null);
  p.then(s.set);
  return s;
}

function flatten<T>(s: Signal<Signal<T>>): Signal<T> {
    return () => s()();
}

function map<A, B>(s: Signal<A>, mapFn: (a: A) => B): Signal<B> {
    return comptued(() => mapFn(s()));
}

class UserService {
    public user = signal({});

    private request = () =>
        fetch('...' + user().id())
            .then(res => res.json())
            .then(response.set);

    public friends = flatten(map(this.request, fromPromise));
}
```

And with that, we've achieved our goal of making the request lazy and memoized.

This is already quite useful, but we're not done yet.
