Effect vs Promise
On this page
In this guide, we will explore the differences between Promise
and Effect
, two approaches to handling asynchronous operations in TypeScript. We'll discuss their type safety, creation, chaining, and concurrency, providing examples to help you understand their usage.
Type safety
Let's start by comparing the types of Promise
and Effect
. The type parameter A
represents the resolved value of the operation:
ts
Promise<A>
ts
Promise<A>
Here's what sets Effect
apart:
- It allows you to track the types of errors statically through the type parameter
Error
. For more information about error management inEffect
, see Expected Errors. - It allows you to track the types of required dependencies statically through the type parameter
Context
. For more information about context management inEffect
, see Managing Services.
Creating
Success
Let's compare creating a successful operation using Promise
and Effect
:
ts
export constsuccess =Promise .resolve (2)
ts
export constsuccess =Promise .resolve (2)
Failure
Now, let's see how to handle failures with Promise
and Effect
:
ts
export constfailure =Promise .reject ("Uh oh!")
ts
export constfailure =Promise .reject ("Uh oh!")
Constructor
Creating operations with custom logic:
ts
export consttask = newPromise <number>((resolve ,reject ) => {setTimeout (() => {Math .random () > 0.5 ?resolve (2) :reject ("Uh oh!")}, 300)})
ts
export consttask = newPromise <number>((resolve ,reject ) => {setTimeout (() => {Math .random () > 0.5 ?resolve (2) :reject ("Uh oh!")}, 300)})
Thenable
Mapping the result of an operation:
map
ts
export constmapped =Promise .resolve ("Hello").then ((s ) =>s .length )
ts
export constmapped =Promise .resolve ("Hello").then ((s ) =>s .length )
flatMap
Chaining multiple operations:
ts
export constflatMapped =Promise .resolve ("Hello").then ((s ) =>Promise .resolve (s .length ))
ts
export constflatMapped =Promise .resolve ("Hello").then ((s ) =>Promise .resolve (s .length ))
Comparing Effect.gen with async/await
If you are familiar with async
/await
, you may notice that the flow of writing code is similar.
Let's compare the two approaches:
ts
constincrement = (x : number) =>x + 1constdivide = (a : number,b : number):Promise <number> =>b === 0?Promise .reject (newError ("Cannot divide by zero")):Promise .resolve (a /b )consttask1 =Promise .resolve (10)consttask2 =Promise .resolve (2)constprogram = async function () {consta = awaittask1 constb = awaittask2 constn1 = awaitdivide (a ,b )constn2 =increment (n1 )return `Result is: ${n2 }`}program ().then (console .log ) // Output: "Result is: 6"
ts
constincrement = (x : number) =>x + 1constdivide = (a : number,b : number):Promise <number> =>b === 0?Promise .reject (newError ("Cannot divide by zero")):Promise .resolve (a /b )consttask1 =Promise .resolve (10)consttask2 =Promise .resolve (2)constprogram = async function () {consta = awaittask1 constb = awaittask2 constn1 = awaitdivide (a ,b )constn2 =increment (n1 )return `Result is: ${n2 }`}program ().then (console .log ) // Output: "Result is: 6"
It's important to note that although the code appears similar, the two programs are not identical. The purpose of comparing them side by side is just to highlight the resemblance in how they are written.
Concurrency
Promise.all()
ts
consttask1 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task1...")setTimeout (() => {console .log ("task1 done")resolve (1)}, 100)})consttask2 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task2...")setTimeout (() => {console .log ("task2 done")reject ("Uh oh!")}, 200)})consttask3 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task3...")setTimeout (() => {console .log ("task3 done")resolve (3)}, 300)})export constprogram =Promise .all ([task1 ,task2 ,task3 ])program .then (console .log ,console .error )/*Output:Executing task1...Executing task2...Executing task3...task1 donetask2 doneUh oh!task3 done*/
ts
consttask1 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task1...")setTimeout (() => {console .log ("task1 done")resolve (1)}, 100)})consttask2 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task2...")setTimeout (() => {console .log ("task2 done")reject ("Uh oh!")}, 200)})consttask3 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task3...")setTimeout (() => {console .log ("task3 done")resolve (3)}, 300)})export constprogram =Promise .all ([task1 ,task2 ,task3 ])program .then (console .log ,console .error )/*Output:Executing task1...Executing task2...Executing task3...task1 donetask2 doneUh oh!task3 done*/
Promise.allSettled()
ts
const task1 = new Promise<number>((resolve, reject) => {console.log("Executing task1...")setTimeout(() => {console.log("task1 done")resolve(1)}, 100)})const task2 = new Promise<number>((resolve, reject) => {console.log("Executing task2...")setTimeout(() => {console.log("task2 done")reject("Uh oh!")}, 200)})const task3 = new Promise<number>((resolve, reject) => {console.log("Executing task3...")setTimeout(() => {console.log("task3 done")resolve(3)}, 300)})export const program = Promise.allSettled([task1, task2, task3])program.then(console.log, console.error)/*Output:Executing task1...Executing task2...Executing task3...task1 donetask2 donetask3 done[{ status: 'fulfilled', value: 1 },{ status: 'rejected', reason: 'Uh oh!' },{ status: 'fulfilled', value: 3 }]*/
ts
const task1 = new Promise<number>((resolve, reject) => {console.log("Executing task1...")setTimeout(() => {console.log("task1 done")resolve(1)}, 100)})const task2 = new Promise<number>((resolve, reject) => {console.log("Executing task2...")setTimeout(() => {console.log("task2 done")reject("Uh oh!")}, 200)})const task3 = new Promise<number>((resolve, reject) => {console.log("Executing task3...")setTimeout(() => {console.log("task3 done")resolve(3)}, 300)})export const program = Promise.allSettled([task1, task2, task3])program.then(console.log, console.error)/*Output:Executing task1...Executing task2...Executing task3...task1 donetask2 donetask3 done[{ status: 'fulfilled', value: 1 },{ status: 'rejected', reason: 'Uh oh!' },{ status: 'fulfilled', value: 3 }]*/
Promise.any()
ts
const task1 = new Promise<number>((resolve, reject) => {console.log("Executing task1...")setTimeout(() => {console.log("task1 done")reject("Something went wrong!")}, 100)})const task2 = new Promise<number>((resolve, reject) => {console.log("Executing task2...")setTimeout(() => {console.log("task2 done")resolve(2)}, 200)})const task3 = new Promise<number>((resolve, reject) => {console.log("Executing task3...")setTimeout(() => {console.log("task3 done")reject("Uh oh!")}, 300)})export const program = Promise.any([task1, task2, task3])program.then(console.log, console.error)/*Output:Executing task1...Executing task2...Executing task3...task1 donetask2 done2task3 done*/
ts
const task1 = new Promise<number>((resolve, reject) => {console.log("Executing task1...")setTimeout(() => {console.log("task1 done")reject("Something went wrong!")}, 100)})const task2 = new Promise<number>((resolve, reject) => {console.log("Executing task2...")setTimeout(() => {console.log("task2 done")resolve(2)}, 200)})const task3 = new Promise<number>((resolve, reject) => {console.log("Executing task3...")setTimeout(() => {console.log("task3 done")reject("Uh oh!")}, 300)})export const program = Promise.any([task1, task2, task3])program.then(console.log, console.error)/*Output:Executing task1...Executing task2...Executing task3...task1 donetask2 done2task3 done*/
Promise.race()
ts
consttask1 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task1...")setTimeout (() => {console .log ("task1 done")reject ("Something went wrong!")}, 100)})consttask2 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task2...")setTimeout (() => {console .log ("task2 done")reject ("Uh oh!")}, 200)})consttask3 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task3...")setTimeout (() => {console .log ("task3 done")resolve (3)}, 300)})export constprogram =Promise .race ([task1 ,task2 ,task3 ])program .then (console .log ,console .error )/*Output:Executing task1...Executing task2...Executing task3...task1 doneSomething went wrong!task2 donetask3 done*/
ts
consttask1 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task1...")setTimeout (() => {console .log ("task1 done")reject ("Something went wrong!")}, 100)})consttask2 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task2...")setTimeout (() => {console .log ("task2 done")reject ("Uh oh!")}, 200)})consttask3 = newPromise <number>((resolve ,reject ) => {console .log ("Executing task3...")setTimeout (() => {console .log ("task3 done")resolve (3)}, 300)})export constprogram =Promise .race ([task1 ,task2 ,task3 ])program .then (console .log ,console .error )/*Output:Executing task1...Executing task2...Executing task3...task1 doneSomething went wrong!task2 donetask3 done*/
FAQ
Question. What is the equivalent of starting a promise without immediately waiting for it in Effects?
ts
consttask = (delay : number,name : string) =>newPromise ((resolve ) =>setTimeout (() => {console .log (`${name } done`)returnresolve (name )},delay ))export async functionprogram () {constr0 =task (2_000, "long running task")constr1 = awaittask (200, "task 2")constr2 = awaittask (100, "task 3")return {r1 ,r2 ,r0 : awaitr0 }}program ().then (console .log )/*Output:task 2 donetask 3 donelong running task done{ r1: 'task 2', r2: 'task 3', r0: 'long running promise' }*/
ts
consttask = (delay : number,name : string) =>newPromise ((resolve ) =>setTimeout (() => {console .log (`${name } done`)returnresolve (name )},delay ))export async functionprogram () {constr0 =task (2_000, "long running task")constr1 = awaittask (200, "task 2")constr2 = awaittask (100, "task 3")return {r1 ,r2 ,r0 : awaitr0 }}program ().then (console .log )/*Output:task 2 donetask 3 donelong running task done{ r1: 'task 2', r2: 'task 3', r0: 'long running promise' }*/
Answer: You can achieve this by utilizing Effect.fork
and Fiber.join
.
ts
import {Effect ,Fiber } from "effect"consttask = (delay : number,name : string) =>Effect .gen (function* (_ ) {yield*_ (Effect .sleep (delay ))console .log (`${name } done`)returnname })constprogram =Effect .gen (function* (_ ) {constr0 = yield*_ (Effect .fork (task (2_000, "long running task")))constr1 = yield*_ (task (200, "task 2"))constr2 = yield*_ (task (100, "task 3"))return {r1 ,r2 ,r0 : yield*_ (Fiber .join (r0 ))}})Effect .runPromise (program ).then (console .log )/*Output:task 2 donetask 3 donelong running task done{ r1: 'task 2', r2: 'task 3', r0: 'long running promise' }*/
ts
import {Effect ,Fiber } from "effect"consttask = (delay : number,name : string) =>Effect .gen (function* (_ ) {yield*_ (Effect .sleep (delay ))console .log (`${name } done`)returnname })constprogram =Effect .gen (function* (_ ) {constr0 = yield*_ (Effect .fork (task (2_000, "long running task")))constr1 = yield*_ (task (200, "task 2"))constr2 = yield*_ (task (100, "task 3"))return {r1 ,r2 ,r0 : yield*_ (Fiber .join (r0 ))}})Effect .runPromise (program ).then (console .log )/*Output:task 2 donetask 3 donelong running task done{ r1: 'task 2', r2: 'task 3', r0: 'long running promise' }*/