This content originally appeared on DEV Community and was authored by kouwei qing
Background Introduction
During the development of IMSDK, the logic is extremely intricate. Take the message retrieval logic as an example: first, retrieve the hot message list by sequence number, then process the messages by iterating through their contents and handling different message types accordingly. Insert the final messages into the message table. If a message initiates a new conversation, insert the conversation information into the conversation table, user information into the user table and user member table, etc. Finally, notify the UI to update with new messages. Many network requests and database operations are asynchronous. In this entire process, numerous asynchronous operations must complete before the next step can proceed. Often, developers inadvertently forget to use await
, leading to inexplicable logical bugs.
When such bugs occur, it is challenging to pinpoint the root cause based solely on the symptoms. Developers must reexamine the logic to check for missing await
statements. This approach is not only inefficient but also prone to oversight. Is there a better solution?
Enter Code Linter
This is where Code Linter comes in handy. It scans your code to help identify potential issues. Run Code Linter before each code submission to check for possible risks.
Using Code Linter
Create a code-linter.json
file in the root directory of your project with the following configuration format:
{
"files": [
"**/*.ets"
],
"ignore": [
"**/src/ohosTest/**/*",
"**/src/test/**/*",
"**/src/mock/**/*",
"**/node_modules/**/*",
"**/oh_modules/**/*",
"**/build/**/*",
"**/.preview/**/*"
],
"ruleSet": [
"plugin:@performance/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/return-await": "error",
"@typescript-eslint/require-await": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/promise-function-async": "error"
}
}
This configuration has three main sections:
-
files: Specifies which files to inspect (typically all
.ets
files). - ignore: Lists files to exclude (e.g., build artifacts, test files).
- ruleSet: Aggregates rule collections (performance recommendations and general rules in this example).
- rules: Defines specific linting rules.
After configuration, right-click on the module to be inspected in your IDE, then select Code Linter > Full Linter:
The inspection results will appear in the Code Linter output:
Modify the non-compliant code based on these results.
Introduction to Asynchronous Rules
We use three asynchronous-specific Code Linter rules:
promise-function-async
The @typescript-eslint/promise-function-async
rule mandates that any function or method returning a Promise
must be marked as async
. This ensures that functions either:
- Return a rejected promise, or
- Throw an
Error
object.
In contrast, non-async
functions returning Promise
can technically do both, complicating error handling. This rule simplifies code by eliminating the need to handle both cases.
Key benefits of promise-function-async
:
- Improves code clarity: Explicitly marks Promise-returning functions.
- Prevents common errors: Avoids overlooked asynchronous operations.
- Standardizes code style: Ensures consistent declaration of Promise-returning functions.
-
Simplifies error handling: Leverages
async
function's automatic error propagation. - Enhances type safety: Validates asynchronous behavior through TypeScript's type system.
Correct usage examples (all Promise-returning functions are marked async
):
export const arrowFunctionReturnsPromise = async () => Promise.resolve('value');
export async function functionReturnsPromise() {
return Promise.resolve('value');
}
// Explicit return type including non-Promise allows non-async function
export function functionReturnsUnionWithPromiseExplicitly(
p: boolean
): string | Promise<string> {
return p ? 'value' : Promise.resolve('value');
}
export async function functionReturnsUnionWithPromiseImplicitly(p: boolean) {
return p ? 'value' : Promise.resolve('value');
}
Non-compliant examples:
export const arrowFunctionReturnsPromise = () => Promise.resolve('value');
export function functionReturnsPromise() {
return Promise.resolve('value');
}
export function functionReturnsUnionWithPromiseImplicitly(p: boolean) {
return p ? 'value' : Promise.resolve('value');
}
Full documentation: Promise Function Async Rule
await-thenable
The @typescript-eslint/await-thenable
rule prohibits using the await
keyword on non-"Thenable" values. A "Thenable" object has a .then()
method (e.g., Promise
).
Correct usage:
async function test() {
await Promise.resolve('value');
}
export { test };
Non-compliant code:
async function test() {
await 'value'; // Error: 'value' is not a Thenable
}
export { test };
Full documentation: Await Thenable Rule
require-await
The @typescript-eslint/require-await
rule is critical. It mandates that async
functions must contain at least one await
expression.
Key objectives:
-
Prevent misuse: Avoid
await
on non-asynchronous values, which is often a mistake. -
Improve clarity: Ensure
await
is used only for genuine asynchronous operations. - Optimize performance: Eliminate unnecessary asynchronous overhead (microtask queue).
-
Enhance type safety: Detect invalid
await
usage via TypeScript types.
Example of missing await
:
async function doSomething(): Promise<void> {
return Promise.resolve();
}
export async function foo() {
doSomething(); // Linter error: Missing `await`
}
The linter flags foo()
for missing await
, helping uncover potential bugs.
Full documentation: Require Await Rule
return-await
The @typescript-eslint/return-await
rule dictates when to use await
in return
statements of async
functions.
In async
functions, both return await promise;
and return promise;
are valid, but return await
offers advantages:
- Improves stack trace readability.
- Captures promise rejections in
try...catch
blocks. - Has negligible performance impact.
This rule enforces consistent handling of returned promises with four configurations:
-
never: Prohibit
await
on returned promises. -
error-handling-correctness-only: Require
await
in error-handling contexts (e.g.,try
/catch
blocks). -
in-try-catch: Require
await
in error-handling; prohibit elsewhere. -
always: Always use
await
when returning promises.
The rule distinguishes between "normal contexts" and "error-handling contexts":
-
Error-handling contexts: Use
return await
to ensurecatch
/finally
blocks execute correctly (e.g., insidetry
/catch
). -
Normal contexts: Style preference; direct
return promise
is acceptable.
Option | Normal Context (Style) | Error-Handling Context (Bug Prevention) | Recommended? |
---|---|---|---|
always |
return await |
return await |
✅ Yes |
in-try-catch |
return promise |
return await |
✅ Yes |
error-handling-only |
No preference | return await |
🟡 Okay |
never |
return promise |
return promise (⚠️ Risky) |
❌ Deprecated |
Example requiring return await
in try...catch
:
export async function validInTryCatch1() {
try {
return Promise.resolve('try'); // Linter error: Missing `await`
} catch (e) {
return Promise.resolve('catch'); // Linter error: Missing `await`
}
}
Best practices:
- Use
"in-try-catch"
for a balanced approach. - Always use
return await
insidetry-catch
blocks. - Return promises directly elsewhere.
- Avoid redundant
await
where error handling isn't needed.
Full documentation: Return Await Rule
no-floating-promises
The @typescript-eslint/no-floating-promises
rule ensures proper handling of Promise expressions. A "floating promise" is one created without error handling, leading to silent failures, incorrect execution order, or uncaught rejections.
The rule flags unhandled Promise expressions unless they are:
- Handled with
.then(onFulfilled, onRejected)
. - Caught with
.catch()
. - Awaited with
await
. - Returned from a function.
- Explicitly ignored with
void
.
It also detects unhandled Promises in arrays. Use Promise combinators like Promise.all()
to handle them collectively.
Examples of unhandled promises:
export async function bar() {
const promise = new Promise<string>(resolve => {
resolve('value');
return 'finish';
});
promise; // Linter error: Unhandled promise
Promise.reject('value').catch(); // Linter error: Empty catch
await Promise.reject('value').finally(); // Linter error: No catch
['1', '2', '3'].map(async x => x + '1'); // Linter error: Unhandled Promise array
}
Key benefits:
- Prevent silent failures: Ensure Promise rejections aren't ignored.
- Improve reliability: Force explicit error handling.
- Simplify debugging: Trace all asynchronous errors.
- Enhance maintainability: Clarify asynchronous data flow.
Full documentation: No Floating Promises Rule
no-misused-promises
The @typescript-eslint/no-misused-promises
rule prevents Promise misuse in non-asynchronous contexts, ensuring Promises are used only where appropriate.
Core objectives:
-
Block conditional misuse: Flag Promises used directly in conditions (e.g.,
if (promise)
). -
Prevent logical operator misuse: Prohibit Promises in
&&
,||
,??
operations. -
Ensure correct callbacks: Prevent async callbacks in synchronous contexts (e.g.,
forEach
). -
Protect void return types: Block Promises in functions expected to return
void
(e.g., event handlers).
Problem scenarios and fixes:
Scenario 1: Conditional misuse
// ❌ Error: Using Promise directly in condition
if (fetchUserData()) {
// Always executes (Promise is always truthy)
console.log('Fetching user data...');
}
// ✅ Correct: Resolve Promise first
async function checkUser() {
const userData = await fetchUserData();
if (userData) {
console.log('User data loaded');
}
}
Scenario 2: Logical operator misuse
// ❌ Error: Using Promise in logical operator
const config = loadConfig() || defaultConfig; // Always uses loadConfig()
// ✅ Correct: Resolve Promise before comparison
async function getConfig() {
const loadedConfig = await loadConfig();
return loadedConfig ?? defaultConfig;
}
Scenario 3: Callback misuse
// ❌ Error: Async callback in array method
[1, 2, 3].forEach(async (id) => {
await deleteRecord(id); // Unintended concurrency
});
// ✅ Correct: Sequential processing with for...of
async function deleteRecords() {
for (const id of [1, 2, 3]) {
await deleteRecord(id);
}
}
// ✅ Correct: Parallel processing with Promise.all
await Promise.all([1, 2, 3].map(id => deleteRecord(id)));
Scenario 4: Void return type misuse
// ❌ Error: Returning Promise from event handler
const button = document.getElementById('submit');
button.addEventListener('click', () => {
return submitForm(); // Event handlers should return void
});
// ✅ Correct: Explicitly ignore Promise
button.addEventListener('click', () => {
void submitForm(); // Indicate Promise is intentionally unhandled
});
// ✅ Correct: Handle errors with async/await
button.addEventListener('click', async () => {
try {
await submitForm();
} catch (error) {
showError(error);
}
});
Full documentation: No Misused Promises Rule
Conclusion
This article addressed issues caused by improper asynchronous operations in HarmonyOS development. By leveraging five specific Code Linter rules, we can effectively mitigate problems related to missing await
keywords and other asynchronous pitfalls.
This content originally appeared on DEV Community and was authored by kouwei qing

kouwei qing | Sciencx (2025-06-28T08:33:59+00:00) HarmonyOS Next IM Practical Combat: Handling Logical Error Bugs Caused by the Lack of await in Asynchronous Operations. Retrieved from https://www.scien.cx/2025/06/28/harmonyos-next-im-practical-combat-handling-logical-error-bugs-caused-by-the-lack-of-await-in-asynchronous-operations/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.