Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 26, 2022 09:47 pm GMT

Promises: Five Tips to Work Smarter

Promises are powerful tools in our increasingly asynchronous world. They help us leave behind the pain of nested callbacks and clarify program flow. However, there are still plenty of misunderstandings about promises that prevent developers from making full use of them.

Here are a few tips that may improve the quality and readability of code in your project.

1. You Don't Need* That Anonymous Function

Examples of promises usually include anonymous arrow functions inside every .then() but you often don't need them. Removing them helps show the readability and flow control promises can provide.

getZero()  .then((number) => addOne(number))  .then((number) => addTwo(number))  .then((number) => addThree(number))  .then((number) => console.log(number));// Let's clean it up. Now we can read it like a sentence.getZero()  .then(addOne)  .then(addTwo)  .then(addThree)  .then(console.log);

Here is actual code from a project I work on. These seven lines are the entire flow of a program. Without reading each function we get a very clear idea of what is happening. (Function names have been improved for clarity.) No arrow function or extra noise gets in the way of expressing the behavior.

portUsed.check(port)  .then(rejectIfUsed)  .then(loadEnvironmentConfig)  .then(startMonitor)  .then(startServer)  .then(logServerDetails)  .catch(reportError);

* Sometimes You Need That Anonymous Function

Of course, there are exceptions. Anonymous functions can be useful or necessary when accessing values outside a promise chain or preserving method context. There are ways around these issues, but they are not always better or easier. Let's take a look.

const state = {  color: 'blue',  setColor(newColor) {    this.color = newColor;  },};// We need some information not in the promise chaingetSystemData()  .then((config) => showColorConfig(state.color, config));

You can use .bind() or techniques like partial application and currying (not covered here) to avoid the anonymous function, but developers with less experience in functional programming may find these harder to follow than an arrow function.

// Bind example to access state outside the chaingetSystemData()  .then(showColorConfig.bind(null, state.color));

Similarly, accessing a method on an object or class instance can be easier with an anonymous function. There are alternatives here, too.

// Regular method on an instance or object.state.setColor('yellow');// BAD, no context. Behavior is unexpected.getColor()  .then(state.setColor);// Good, preserve method contextgetColor()  .then((color) => state.setColor(color));// Also OK, bind method contextgetColor()  .then(state.setColor.bind(state));

These choices often come down to conventions of a project and the comfort of the developers.

2. Promise Everything

One of the biggest features of Promises is the continuation of the promise chain. Any non-promise function response is wrapped in a resolved promise, so promise chains automatically allow any function in the chain to be sync or async.

All you have to do to get this benefit is start with a Promise. Using Promise.resolve() is a simple way to reliably open a promise chain. After that, you don't need to know if something is async; it just works.

Promise.resolve()  .then(getFirstValue)  .then(addOne)  .then(addRandomValue)  .then(saveValueToServer)  .then(displayValue)  .catch(displayError);

From a glance you cannot tell which of these functions are async, and at this high level it doesn't matter. You can still read the flow of the code, and it just runs. As a project grows and changes, it is trivial to change functions from sync to async if they already live in a promise chain.

Starting a chain with Promise.resolve() means even the very first action in your promise chain the one almost guaranteed to be async can be swapped out with a synchronous version. This can be a big advantage for writing tests.

We can replace any async method with a synchronous mockup to check if the rest of the program behaves as expected. We can mock up resolved or rejected promises for different test scenarios. And, as our code evolves, we can swap each function between sync and async without touching this code.

3. Try/Catch For Free

Promises capture thrown errors as a rejected promise. This can replace most try/catch logic. Every function in a promise chain gets this wrapping, so we get error handling no matter which function fails.

Async/await requires us to add error handling anywhere we need it, and we can still see uncaught errors if we assume a function does not fail. This is a bigger risk if updates introduce errors later in the project.

There's a lot of details and options in error handling, so let's break it down a bit.

The Basics

When the code is pretty simple, we can just wrap everything in a try/catch. At this level, the difference between try/catch and promises is minor.

// Everything in try/catch for simplicitytry {  const settings = await getUserSettings();  const data = JSON.parse(settings);  displayUserSettings(data);} catch(e) {  displayErrorPage(e);}Promise.resolve()  .then(getUserSettings)  .then(JSON.parse)  .then(displayUserSettings)  .catch(displayErrorPage);

Recoverable Errors

But what if we have more requirements? What if the program can recover from certain errors? Maybe we want to display default user settings if getUserSettings fails.

With try/catch blocks, we must anticipate each possible failure to handle it.

let data;try {  const settings = await getUserSettings();  // JSON.parse can throw an error, too.  data = JSON.parse(settings);} catch {  data = useDefaultSettings();}try {  displayUserSettings(data);} catch(e) {  displayErrorPage(e);}

Multiplying Errors

If functions in our "recoverable" catch block can error, we need to nest try/catch blocks. For this we can make different functions to add layers of error handling without deep indentation making things harder to follow.

// Separate logic to avoid nestingconst tryUserSettings = async () => {  try {    const settings = await getUserSettings();    return JSON.parse(settings);  } catch {    return useDefaultSettings();  }};try {  // Handling more possibilities.  const data = await tryUserSettings();  displayUserSettings(data);} catch(e) {  displayErrorPage(e);}

Now we have error handling around everything, but it's harder to follow the program flow. But we can do better. Because await works on any promise, we can blend together promise chains and async/await.

// Simple await.catch() patternconst data = await mainAction().catch(backupAction);

This style can help group certain actions and error handlers more neatly than multiple try/catch blocks while keeping all our logic together.

// A bit of both - await a promise chain// All of "get data" is now one block//  and we catch errors from useDefaultSettingstry {  const data = await getUserSettings()    .then(JSON.parse)    .catch(useDefaultSettings);  displayUserSettings(data);} catch(e) {  displayErrorPage(e);}

It took a bit, but we've handled all the errors, even from useDefaultSettings.

The Promise Way

Let's implement the same logic in a promise chain. With promises, we can use .catch() along the way to support fallbacks or generic settings when the intended behavior fails. We also benefit from Tip #2 in that we don't need to know which functions are async.

Promise.resolve()  .then(getUserSettings)  .then(JSON.parse)  .catch(useDefaultSettings)  .then(displayUserSettings)  .catch(displayErrorPage);

Promises can handle the errors with less code, fewer assumptions about the functions, and a clearer program flow.

4. Avoid Accidental Serialization

async/await is great for writing code that looks synchronous, but isn't. In some circumstances, that synchronous-looking code can cause delays. Using pure promises or a combination of Promise methods and async/await can keep your code running at peak performance. Here are some code examples of the problem and some alternatives.

// Accidental Serializationconst getProductDetails = async () => {  // Await stops the code, so getProductList  //  won't start until getUserSettings is done.  const user = await getUserSettings();  const products = await getProductList();  // Did products need to wait for user?  console.log(user, products);};// Prevent Serializationconst getProductDetails = async () => {  // These variables are promises.  // Both requests start immediately.  const user = getUserSettings();  const products = getProductList();  // No waiting, but we must remember they are promises.  console.log(await user, await products);};// Hybrid solutionconst getProductDetails = async () => {  // No serialization thanks to Promise.all.  // await gets us out of the promise chain.  const [user, products] = await Promise.all([    getUserSettings(),    getProductList(),  ]);  console.log(user, products);};// Only Promises - No async neededconst getProductDetails = () => {  Promise.all([    getUserSettings(),    getProductList(),  ]).then(([user, products]) => {    console.log(user, products);  });};

These situations often come up in a project when adding features or complexity. Whether you are using promises alone or blending them with async/await, promises are one of the best options for ensuring you don't make your code or your user wait longer than needed.

5. Promises Aren't a Panacea

While promises do a great job of encapsulating complex details so you can read a high-level program flow, there are places where the trying to fit everything into promises can get messy. Complex or repeating behaviors like request retries and polling may be easier to read and reason about outside of a promise chain.

I find them best when you start thinking from the high-level flow of a project. You can use a simple promise chain to sketch out what you want your code to do...

const eureka = () => Promise.resolve()  .then(connectToHomeServer)  .then(loadSmartDeviceList)  .then(selectOnlyLights)  .then(turnOnDevices);

...and then make it happen.


Original Link: https://dev.to/oculus42/promises-five-tips-to-work-smarter-mme

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To