First of all, I'm guessing that you're intentionally building your own observable to learn somsething, instead of using a library like rxjs, so I'm gonna focus on why your current implementation doesn't work. For production usage, I would highly recommend to use a battle tested library for observables instead of building your own.
Now to your code.
1. You've mixed up the subscription and the emission (fire in your case)
Right now, your Observable will execute every function passed to subscribe if you call fire. But None of the observers will ever get the API result, because you don't pass it to fire. You should modify your observable like this:
export function Observable() {
this.observers = []; //array of observer functions
}
Observable.prototype = {
subscribe: function (fn) {
this.observers.push(fn);
},
// add parameter value
fire: function (value) {
this.observers.forEach((fn) => {
// call the observer callbacks directly with value
fn(value);
});
}
};
2. You never "fire" your observable with the API result.
Instead, you've passed the Promise that returns the bitcoin price to the subscribe function. As said before, this way, no other observer could ever receive the API result.
You should modify your useEffect this way:
React.useEffect(() => {
const bitcoinObservable = new Observable();
bitcoinObservable.subscribe((livePrice) => {
console.log("sub!", { livePrice });
setPrice(livePrice);
});
getBitcoinPrice().then((livePrice) => {
console.log('fire!', { livePrice });
bitcoinObservable.fire(livePrice);
});
}, [])
Now your code sandbox example already works fine. Of course, creating the observable and subscribing to it within the same useEffect call doesn't make much sense. But if you want to use your observable in more than one place, there is some other thing you should do:
3. Add unsubscribe logic to avoid memory leaks
React components get mounted and unmounted all the time, and the useEffect logic with them. If you don't unsubscribe from the observable, you could cause memory leaks. So you should add an unsubscribe function like this to your Observable:
Observable.prototype = {
subscribe: function (fn) {
this.observers.push(fn);
// return an object with the unsubscribe function
return {
unsubscribe: () => {
this.observers.splice(this.observers.indexOf(fn));
}
};
},
// ...
};
Then in your useEffect, you can unsubscribe within the effects cleanup callback:
React.useEffect(() => {
// ...
// save the return object of the subscribe call
const subscription = bitcoinObservable.subscribe((livePrice) => {
console.log("sub!", { livePrice });
setPrice(livePrice);
});
// ...
// add cleanup logic
return () => {
subscription.unsubscribe();
};
}, []);
This will cleanup your subscription callbacks if the component gets destroyed. This will be necessary as soon as you use your observable in more place than one.
If you want to want to know more about observables, I recommend articles like this one: https://indepth.dev/posts/1155/build-your-own-observable-part-1-arrays