← Back

PROMISIFICATION IN JAVASCRIPT

Promisification means changing a function so that it returns a promise instead of accepting callbacks.

This note explains promisification in simple language.

You will learn:

  1. what promisification is
  2. the difference between callbacks and promises
  3. how to turn a callback function into a promise-based function
  4. how Promise.resolve() works
  5. how Promise.reject() works
  6. how to promisify synchronous functions
  7. how to create delayed promises with a reusable function

1. What is promisification?

Promisification means changing a function so that it no longer accepts callbacks, but instead returns a promise.

A function changed in this way is called a promisified function.

Easy definition:

Old version:
function takes callbacks

Promisified version:
function returns a promise

Diagram 1. Main idea

Callback-style function
-> depends on external callbacks

Promisified function
-> returns promise
-> outer code handles result with then/catch

The function should focus on doing its job. It should not worry about how outer code wants to use the result.

2. Callbacks vs promises

Imagine a function that requests user data from a server.

Callback version

const fetchUserFromServer = (username, onSuccess, onError) => {
  // ...
};

This function expects:

That means the function knows too much about the outside code, because it expects other functions to be passed in and then must decide when to call them.

Diagram 2. Callback style

Outer code
-> passes callbacks into function
-> function decides when to call them

This works, but it makes the function more dependent on external code.

3. Main differences between callbacks and promises

Important differences:

Diagram 3. Callback vs promise

Callback
-> function passed as argument

Promise
-> object returned from function

Easy rule:

callback = you give function to async code
promise  = async code gives object back to you

4. Callback version of async code

const fetchUserFromServer = (username, onSuccess, onError) => {
  console.log(`Fetching data for ${username}`);

  setTimeout(() => {
    const isSuccess = true;

    if (isSuccess) {
      onSuccess("success value");
    } else {
      onError("error");
    }
  }, 2000);
};

fetchUserFromServer(
  "Mango",
  user => console.log(user),
  error => console.error(error)
);

What happens here?

  1. the function starts an asynchronous action
  2. it waits 2 seconds
  3. it checks isSuccess
  4. if true, it calls onSuccess
  5. if false, it calls onError

Diagram 4. Callback flow

Call function
-> wait 2 seconds
-> check isSuccess
   |- true  -> call onSuccess(...)
   `- false -> call onError(...)

5. Why promisification is better

It is better if the function does not care about the external result-handling code.

It should simply do the operation and return its result as a promise.

Then the outer code can decide how to handle success or error with then() and catch().

Diagram 5. Better design

Function
-> does async work
-> returns promise

Outer code
-> handles result with then/catch

This makes the function more independent and cleaner.

6. First step of promisification

To promisify the function, start by returning a promise:

const fetchUserFromServer = username => {
  return new Promise((resolve, reject) => {
    // ...
  });
};

Now the function no longer takes onSuccess and onError.

Instead, it creates a promise and returns it.

Diagram 6. First transformation

Before:
(username, onSuccess, onError)

After:
(username) => Promise

7. Full promisified version

const fetchUserFromServer = username => {
  return new Promise((resolve, reject) => {
    console.log(`Fetching data for ${username}`);

    setTimeout(() => {
      const isSuccess = true;

      if (isSuccess) {
        resolve("success value");
      } else {
        reject("error");
      }
    }, 2000);
  });
};

fetchUserFromServer("Mango")
  .then(user => console.log(user))
  .catch(error => console.error(error));

What changed?

Diagram 7. Promisified flow

Call function
-> function returns promise
-> wait 2 seconds
-> check isSuccess
   |- true  -> resolve("success value")
   `- false -> reject("error")

Outer code
-> then(...) or catch(...)

8. Handling the returned promise

You can store the promise in a variable:

const userPromise = fetchUserFromServer("Mango");

userPromise
  .then(user => console.log(user))
  .catch(error => console.error(error));

But in practice, developers usually attach handlers directly:

fetchUserFromServer("Mango")
  .then(user => console.log(user))
  .catch(error => console.error(error));

The second version is shorter and usually more convenient.

Diagram 8. Two ways to use returned promise

Option 1:
save promise in variable
-> add then/catch later

Option 2:
call function
-> attach then/catch immediately

9. Promise.resolve() and Promise.reject()

These are static methods of the Promise class.

They create promises that are already settled:

They are shorter alternatives to writing new Promise(...) for simple cases.

Diagram 9. Quick promise creation

Promise.resolve(value)
-> fulfilled promise

Promise.reject(error)
-> rejected promise

10. Example of Promise.resolve()

Longer version

new Promise(resolve => resolve("success value"))
  .then(value => console.log(value))
  .catch(error => console.log(error));

Shorter version

Promise.resolve("success value")
  .then(value => console.log(value))
  .catch(error => console.log(error));

Both create a fulfilled promise, but the second version is simpler.

Diagram 10. Promise.resolve()

"success value"
-> Promise.resolve(...)
-> fulfilled promise
-> then(...) runs

11. Example of Promise.reject()

Longer version

new Promise((resolve, reject) => reject("error"))
  .then(value => console.log(value))
  .catch(error => console.log(error));

Shorter version

Promise.reject("error")
  .then(value => console.log(value))
  .catch(error => console.log(error));

This creates a rejected promise, so catch() runs.

Diagram 11. Promise.reject()

"error"
-> Promise.reject(...)
-> rejected promise
-> catch(...) runs

12. Promisifying a synchronous function

Promise.resolve() and Promise.reject() are useful even when the original function is synchronous, but you still want it to return a promise and fit into a promise chain.

13. Callback version of a synchronous function

const makeGreeting = (guestName, onSuccess, onError) => {
  if (!guestName) {
    onError("Guest name must not be empty");
  } else {
    onSuccess(`Welcome ${guestName}`);
  }
};

makeGreeting(
  "Mango",
  greeting => console.log(greeting),
  error => console.error(error)
);

This function does not wait for any async action, but it still uses callbacks.

14. Promisified synchronous version with new Promise

const makeGreeting = guestName => {
  return new Promise((resolve, reject) => {
    if (!guestName) {
      reject("Guest name must not be empty");
    } else {
      resolve(`Welcome ${guestName}`);
    }
  });
};

makeGreeting("Mango")
  .then(greeting => console.log(greeting))
  .catch(error => console.error(error));

This already works as a promisified version.

15. Shorter version with Promise.resolve() and Promise.reject()

const makeGreeting = guestName => {
  if (!guestName) {
    return Promise.reject("Guest name must not be empty");
  }

  return Promise.resolve(`Welcome ${guestName}`);
};

makeGreeting("Mango")
  .then(greeting => console.log(greeting))
  .catch(error => console.error(error));

This is shorter and easier to read.

Diagram 12. Synchronous promisification

Function checks value
|- bad value  -> Promise.reject(...)
`- good value -> Promise.resolve(...)

This is useful when you want all your functions to work in the same promise style.

16. Creating delayed promises

Next, create a reusable function called makePromise(options).

This function returns promises with different:

17. Function structure

const makePromise = ({ value, delay, shouldResolve = true }) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldResolve) {
        resolve(value);
      } else {
        reject(value);
      }
    }, delay);
  });
};

Parameters:

The default value of shouldResolve is true.

Diagram 13. makePromise() logic

makePromise({ value, delay, shouldResolve })
-> wait delay
-> check shouldResolve
   |- true  -> resolve(value)
   `- false -> reject(value)

18. Using makePromise()

makePromise({ value: "A", delay: 1000 })
  .then(value => console.log(value))
  .catch(error => console.log(error));

makePromise({ value: "B", delay: 3000 })
  .then(value => console.log(value))
  .catch(error => console.log(error));

makePromise({ value: "C", delay: 2000, shouldResolve: false })
  .then(value => console.log(value))
  .catch(error => console.log(error));

What happens?

Diagram 14. Three delayed promises

Promise A
-> 1 sec
-> resolve "A"

Promise B
-> 3 sec
-> resolve "B"

Promise C
-> 2 sec
-> reject "C"

This is useful because you write the delayed promise logic once and reuse it many times.

19. Why makePromise() is useful

Without this reusable function, you would have to write new Promise(...) and setTimeout(...) again and again for every delayed promise.

The helper function removes repetition and makes the code cleaner.

Diagram 15. Repetition vs reusable function

Without helper
-> write same promise code many times

With makePromise()
-> write logic once
-> reuse it with different options

20. Easy memory rules

Promisification = change callback function into promise-returning function

Callback style
-> function accepts onSuccess / onError

Promisified style
-> function returns promise
-> outer code uses then/catch

Promise.resolve(value)
-> fulfilled promise

Promise.reject(error)
-> rejected promise

21. Quick summary

22. Final conclusion

If you understand these ideas:

promisification
callback vs promise
returning a promise
Promise.resolve()
Promise.reject()
promisifying sync functions
delayed promises

then you already have a strong foundation for working with promise-based JavaScript code.

← Back