Skip to content

A Visual Guide to Refactoring Callback Functions to Promises & Async/await

5 min read

Are you constantly struggling to keep your code at least halfway understandable while having deeply nested calls everywhere?

Callback trees a million deep are distressing.

Perhaps you're still not comfortable with async/await and you're stuck using promises.

But what if you understood how async/await works, what would you accomplish? A successful job interview, recognition for your skills, or maybe a promotion?

Imagine working with code that's easy to understand and change, how would that change how you feel about your work?

By learning the simple approach of identifying and isolating the individual parts involved in asynchronous code flow, you will avoid introducing bugs in the refactoring process.

You'll learn a new skill that will give you the confidence to turn callback hells into async joys.

A primer on Node.js callback convention

Callbacks can be either synchronous or asynchronous. When talking about asynchronous callbacks in Node.js, the following two points are true in most cases:

  1. The callback function is always the last argument passed to an asynchronous function, preceded by other arguments (if any):
// The callback function is the last argument to an asynchronous function
asyncFunction(...params, callback);
  1. If an asynchronous operation fails, the error object will be the first argument passed to the callback function. In case of a success, the error argument will be null followed by 0, 1 or more return values:
// An error-first callback function
callback(error, ...results) {
  if (error) {
    // Handle error
    return;
  }

  // Do something with the result...
}

This error-first callback style has become a standard in the Node.js community. It's a familiar pattern that makes working with asynchronous code easier.

Parts of asynchronous code flow

Asynchronous code can be broken into a few different parts. Identifying and isolating these individual parts before refactoring is key to not breaking your code in the process.

The five parts are:

  • Function execution (with arguments, if any)
  • Error object
  • Return value(s)
  • Error handling
  • Using return value(s)

Throughout this article, we'll use reading the contents of a file in Node.js as an example. We'll start with the callback approach, then refactor that into a promise, and lastly refactor to use async/await.

Here's an exercise for you — before reading on, try to identify and isolate all five parts in the following code snippet.

Using fs.readFile to read a file and passing a callback function with two arguments: error and data. If the error exists it is handled first and otherwise the return value is used.
Reading a file in Node.js using a callback function

Go ahead, I'll wait.

.
.
.
.
.
.
.
.
.
.

Did you correctly identify all parts involved in asynchronous code flow? Compare your answer with the image below:

Reading a file in Node.js with fs.readFile and a callback function. Five parts of asynchronous code flow are circled: function execution, error object, return value, error handling and using the return value.
Individual parts of asynchronous code flow

The return keyword is an Return Early Pattern to avoid wrapping and indenting the remaining code in an else block. Returning nothing is the same as not using the keyword at all — in both cases the function's return value is undefined. In the promise & async/await examples further in this article the return keyword is left out, and because that doesn't change the function definition it's safe to exclude it from the error handling part.

Refactoring callback functions to promises

Once you've identified and isolated the individual parts, you're ready to refactor the callback function to use its promise counterpart.

While refactoring, it's important to remember to not change anything internal to the individual parts.

Refactoring a callback function to a promise is done by moving the parts as a whole and putting them together in a different way.

The following animation explains this process visually:

The second fs.readFile is the promise-based equivalent from the fs/promises Node.js API namespace. Most libraries provide promise alternatives to callback functions. In case yours doesn't, or if you're depending on custom legacy code, you can promisify a callback-based asynchronous function.

The parts that are handling the error and using the return value are short one-liners for example purposes. In your situation, they will likely be much bigger, but the principle remains the same — the parts should be moved as a whole unit without modifying them or breaking them apart.

A noticeable difference between callback functions and promises is that error handling (failure) is separated from using the return value (success). This visual separation is a better representation of the two diverging code paths and is, therefore, is easier to work with.

Refactoring promises to async/await

Refactoring callback functions straight to async/await involves multiple steps and will take some practice before you get the hang of it.

It might be easier and less error-prone to add an intermediary step to the refactoring process. First, refactor the callback function to a promise, and only then refactor the promise to use async/await.

This is how the transition from a promise to async/await looks visually:

Notice how much less movement there is compared to the previous animation that went from a callback function to a promise. Because the success and failure parts are separately kept, refactoring a promise to async/await is mostly about changing the syntax.

Conclusion

It takes a lot of practice before you're able to effortlessly refactor callback functions into promises & async/await.

By first identifying and isolating the individual parts involved in asynchronous code flow, you're less likely to break your application while refactoring.

Now it's your turn to get rid of nightmare-inducing legacy code and do a long-awaited (pun not intended) cleanup. The codebase will be easier to read, maintain, and most importantly, a joy to work with. ✨

In case you need to brush up your understanding of async/await, go ahead and give the article a read.

Turn deeply nested callback trees into easy-to-read asynchronous code

Learn how to turn unmaintainable code into code that is easy to read and change with a FREE 5-day email course.

You'll get the Refactoring Callbacks Guide that has visual explanations of how to convert nested callbacks to async/await. Using a simple yet effective 5-step approach, you'll gain the confidence to refactor deeply nested callback hells without introducing new bugs.

Moreover, with 30+ real-world exercises you'll transfer knowledge into a practical skill that will greatly benefit your career.

Get Lesson 1 now 👇🏼

You'll also get tips on building scalable Node.js applications about twice a month. I respect your email privacy. Unsubscribe any time.

You might also like

玻璃钢生产厂家通道商场美陈报价昆明户外玻璃钢雕塑定制曲阳玻璃钢动物雕塑定制五一商场美陈方案呈贡玻璃钢雕塑设计公司多少钱河北玻璃钢花盆批发价格青岛大型商场创意商业美陈济宁仿真蔬菜玻璃钢雕塑玻璃钢雕塑使用技巧户外玻璃钢雕塑摆件国内制作秦淮商场美陈温州家用玻璃钢花盆许昌景观校园玻璃钢雕塑价格玻璃钢人物雕塑制作视频长春小品系列玻璃钢雕塑公司公园玻璃钢雕塑销售江苏水果玻璃钢雕塑多少钱锦州玻璃钢雕塑精选厂家绵阳玻璃钢仿铜雕塑定制杭州水果玻璃钢雕塑价位江苏玻璃钢造型雕塑价格宁德玻璃钢仿真水果雕塑价格雪梨玻璃钢雕塑设计厂家商场美陈雕塑玻璃钢定制山东玻璃钢广场雕塑呈贡玻璃钢雕塑的设计贵不贵深圳佛像玻璃钢雕塑玻璃钢红军雕塑咨询晋城不锈钢景观玻璃钢彩绘雕塑溧阳玻璃钢价值观雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化