返回首页

Swift Concurrency Series 02|Problems solved by async/await

It is to make the asynchronous system regain the conditions for long-term maintenance

If you only look at the surface, async/await seems to mainly solve three problems:

  • too many callbacks
  • Nesting too deep
  • The code is too ugly

But after actually using it in the project for a period of time, you will find that these are just appearances. What it really solves is: **Asynchronous business can finally be organized, reasoned, and reused like normal business. **

This is an engineering maintainability change.

1. The first thing it solves is: business processes are no longer hijacked by writing methods

Many asynchronous businesses themselves are not complicated. What is complicated is that the old writing method makes it complicated.

For example, a real but common process:

  1. Pull user information
  2. Check membership status
  3. Pull recommended content on the homepage
  4. If one of the steps fails, fall back to the full display
  5. Last updated page status

This would have been a very clear sequential process.

But in the era of completion, we often have to deal with these “writing issues” first:

  • Where is the successful branch written?
  • Where is the failed branch written?
  • Where is the main thread switching written?
  • Which layer is responsible for early return? -Which layer is responsible for unifying bomb errors?

Then the complexity no longer comes from the business, but from the expression itself. async/await The most direct benefit is to remove this extra layer of noise and return the business complexity to the business itself.

2. It makes “dependency” clear again

Many asynchronous processes are strongly dependent.

For example:

  • Only after getting the login status can you get the user information
  • Only after obtaining the user information can you decide which modules to load on the homepage.
  • Only after getting the experimental configuration can you decide the page display path

Of course, completion can also express these relationships, but the problem is: it often hides dependencies in layers of nesting. The code can run, but the dependency chain is no longer clear.

One of the core values of async/await is to bring this “who depends on whom” relationship back to a linear control flow:

let session = try await authService.loadSession()
let user = try await userService.loadUser(session: session)
let modules = try await homeService.loadModules(for: user)

The value of this code is not just that it looks good, but that it can be seen immediately:

  • Sequential relationship
  • break point
  • Which step may be wrong?

This will directly affect your confidence when modifying requirements later, because you can finally determine where the section you are moving is in the entire link.

3. It significantly improves error propagation

In the old asynchronous model, error handling was the most fragile.

Because every level of completion may fail, this often ends up in this situation:

  • A set of handling for first-tier request failure
  • Another set of treatments for second layer failure
  • If the third level fails, try another set.
  • Some places swallowed it wrong
  • Play toast in some places
  • Some places only log

You will find that the real trouble is that the error path is broken up like the main process.

async/await at least gives a unified error propagation model:

  • Dispose of this layer
  • Just keep throwing upwards
  • Unified responsibility from higher levels

In other words, it does not decide for the team “how to handle errors”, but it at least makes “how errors flow” clear again. This is especially important for business boundaries.

4. It explicitly exposes the pause point

A common situation is to ignore the most important meaning of await: It’s a reminder.

It’s telling:

  • Possible pause here
  • The context here may vary
  • The code after this should not be in the exact same environment by default

This is key to concurrent thinking.

Because the real danger of asynchronous code is that after calling it, you often subconsciously think:

  • The current page is still there
  • The current status has not changed
  • The current object remains intact

And await at least clearly marks “this is the boundary” grammatically. This will force you to start thinking seriously about state validity, rather than treating asynchronous as an ordinary function call.

5. It makes it easier to enter the main design instead of plug-in logic.

In the past, many codes could also do cancellation, but cancellation was usually like a catch-up function:

  • Save a token separately
  • Remember to cancel when the page is destroyed
  • Manually stop old requests in some scenarios

Can it be done? Of course you can. But the problem is that cancellation in the old model is often not part of the main process, but like an external patch.

In Swift Concurrency, tasks and cancellation are finally more naturally viewed as a set of things. This forces several key questions to be asked earlier:

  • Who owns this task?
  • Should it stop when the page is left?
  • New tasks are coming, should old tasks become invalid?

It doesn’t automatically help get cancellation right, but it brings to the forefront the fact that cancellation should be designed into it.

6. It improves not only personal experience, but also the consistency of team collaboration.

This is a common situation that is underestimated.

When a person writes code, the difference between completion and async/await may only be reflected in smoothness. But in multi-person collaboration projects, the really important thing about async/await is that it makes the team’s expression more unified.

Now at least it’s possible to default to:

  • Asynchronous capabilities will appear in the async function
  • Failed with throw
  • Use await for pause point

This unification is very important. Because the biggest fear in complex projects is that everyone uses their own asynchronous organization method. Sooner or later the project will become runnable, but no one will be able to take it over quickly.

7. It doesn’t solve anything

At this point, we should also make it clear that async/await does not solve these problems:

  • It does not automatically eliminate race conditions
  • Does not automatically prevent old requests from overwriting new results
  • Will not decide for the team how the status boundaries should be drawn
  • Does not automatically ensure that UI updates occur in the correct context

So if a project’s original asynchronous design is very messy, it may just be “better to look messy” after it is replaced with async/await.

This must be seen clearly. async/await is not an architectural panacea, it just illuminates many problems that were originally covered up by writing.

8. Conclusion: What it solves is “Can asynchronous code still be maintained for a long time?”

To put it in shorter form, I would say:

async/await The real solution is that asynchronous code becomes increasingly difficult to organize, reason about, and maintain in complex projects.

So a more accurate statement is not:

“It makes the code shorter.”

Instead:

“It brings asynchronous code back into the structural condition for long-term maintainability.”

FAQ

读完之后,下一步看什么

如果还想继续了解,可以从下面几个方向接着读。

Related

继续阅读

这里整理了同分类、同标签或同类问题的文章。