Practical Frontend Techniques: Vite Auto Routing, await-to-js Source Walkthrough, URL Parameter Handling, Controlling forEach Loops, and Singleton Request Management
This article presents practical frontend solutions, including automatic route generation in Vite projects using unplugin-vue-router, a detailed source analysis of the await‑to‑js error‑handling wrapper, methods for extracting URL parameters, techniques to terminate or replace forEach loops, and patterns for avoiding duplicate component requests.
1. Vite Project Automatic Route Configuration
When developing a Vite project, adding a new page often requires manually updating the router, which can be tedious. Using the unplugin-vue-router plugin, developers can automatically generate route files for each .vue component.
1.1 Pain Points
Do you have to add a route entry every time you create a new page?
Would you like a .vue file to automatically generate its corresponding route?
Solution: install and configure unplugin-vue-router in vite.config.ts as shown below.
import VueRouter from 'unplugin-vue-router/vite'
export default defineConfig({
plugins: [
VueRouter({
routesFolder: 'src/views',
exclude: ['**/components/*.vue'],
extensions: ['.vue']
}),
// ⚠️ Must be placed before Vue()
Vue()
]
})Additional configuration example:
VueRouter({
routesFolder: 'src/pages',
extensions: ['.vue'],
exclude: [],
dts: './typed-router.d.ts',
routeBlockLang: 'json5',
importMode: 'async'
})Resulting folder structure:
src/views/
├── index.vue
├── about.vue
└── users.vueGenerated routes:
/ → renders index.vue
/about → renders about.vue
/users → renders users.vue
2. await-to-js Source Code Analysis
The library is described as an "Async await wrapper for easy error handling". It simplifies error handling by avoiding repetitive try/catch blocks when using async/await .
2.1 Usage
import to from 'await-to-js';
const [err, res] = await to(somePromise({ userId: 'demoId', name: 'demoName' }));
if (err) return console.error(err);
console.info(res);The function always returns a two‑element array where the first element is the error (or null ) and the second is the result.
2.2 Source Walkthrough
/**
* @param { Promise } promise
* @param { Object= } errorExt - Additional information to merge into the error object
* @return { Promise }
*/
export function to
(
promise: Promise
,
errorExt?: object
): Promise<[U, undefined] | [null, T]> {
return promise
.then<[null, T]>(data => [null, data])
.catch<[U, undefined]>(err => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
export default to;Key points:
The function accepts a required Promise and an optional extra error object.
It always resolves to a length‑2 array: [error, result] .
Internally it uses then and catch to separate success and failure, optionally merging extra error info with Object.assign() .
3. Getting URL Parameters
3.1 Basic Retrieval
Given a URL like https://www.example.com?name=Tom&age=18&gender=male :
const search = window.location.search; // "?name=Tom&age=18&gender=male"
const params = new URLSearchParams(search);
const name = params.get('name'); // "Tom"
const age = params.get('age'); // "18"
const gender = params.get('gender'); // "male"3.2 Edge Cases
If there are no query parameters, window.location.search returns an empty string.
Duplicate parameter names are merged; getAll() can retrieve all values.
Requesting a non‑existent key returns null .
3.3 Utility Function
A reusable function getUrlParams(url, key) returns the value or null :
function getUrlParams(url, key) {
const search = new URL(url).search;
const params = new URLSearchParams(search);
return params.get(key);
}
const url = 'https://www.example.com?name=Tom&age=18&gender=male';
const name = getUrlParams(url, 'name'); // "Tom"
const hobby = getUrlParams(url, 'hobby'); // nullFurther enhancements can support multiple keys, default values, or directly accept a URL object.
4. How to Terminate a forEach Loop
Because return and break do not stop a forEach , the following workarounds are shown:
4.1 Throw an Error
const array = [-3, -2, -1, 0, 1, 2, 3];
try {
array.forEach(it => {
if (it >= 0) {
console.log(it);
throw Error(`We've found the target element.`);
}
});
} catch (err) {
// handle termination
}4.2 Set Length to Zero
const array = [-3, -2, -1, 0, 1, 2, 3];
array.forEach(it => {
if (it >= 0) {
console.log(it);
array.length = 0;
}
});4.3 Use splice to Remove Remaining Elements
const array = [-3, -2, -1, 0, 1, 2, 3];
array.forEach((it, i) => {
if (it >= 0) {
console.log(it);
array.splice(i + 1, array.length - i);
}
});5. forEach Cannot Handle Asynchronous Tasks
5.1 Problem Demonstration
Using forEach with async functions leads to nondeterministic order because the loop does not await each promise.
async function processRoles() {
let roles = ['admin', 'editor', 'employee'];
roles.forEach(async role => {
const result = await performTask(role);
console.log(result);
});
console.log('All tasks dispatched');
}
function performTask(role) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Processed role: ${role}`);
}, Math.random() * 1000);
});
}
processRoles();Expected sequential output is often scrambled.
5.2 Solution: Use for…of
async function processRoles() {
const roles = ['admin', 'editor', 'employee'];
for (const role of roles) {
const result = await performTask(role);
console.log(result);
}
console.log('All tasks completed');
}5.3 Using an Iterator Directly
Iterators expose value and done properties, allowing manual control of async sequencing.
let roles = ['admin', 'editor', 'employee'];
let iterator = roles[Symbol.iterator]();
let res = iterator.next();
while (!res.done) {
console.log(res.value);
console.log(await performTask(res.value));
res = iterator.next();
}
console.log('All tasks completed');6. Multiple Identical Component Duplicate Requests
When several identical components mount simultaneously, each may trigger the same API request, causing redundant network traffic. Two singleton‑based solutions are presented.
6.1 Simple Singleton with Cache
let cache = null;
let count = 0;
async function delay(ms = 200) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export async function getSignature() {
if (cache) return cache;
if (count++) {
while (!cache) await delay();
} else {
cache = await fetchSignature();
}
count--;
return cache;
}6.2 Asynchronous Singleton Pattern
Using a shared promise ( myfetch ) ensures that concurrent calls receive the same pending result.
function getSomething() {
return new Promise(resolve => {
const result = fetch('http://hn.algolia.com/api/v1/search?query=vue')
.then(val => resolve(val.json()))
})
}
let myfetch = null;
async function getData() {
if (myfetch) return myfetch;
myfetch = getSomething();
try {
const result = await myfetch;
myfetch = null;
return result;
} catch (err) {
console.error('err');
}
}
const result1 = getData();
const result2 = getData();
const result3 = getData();
const result4 = getData();
console.log('123');
setTimeout(() => {
console.log(result1, 'result1');
console.log(result2, 'result2');
console.log(result3, 'result3');
console.log(result4, 'result4');
}, 1500);All components receive the same resolved data without issuing multiple HTTP requests.
References
[1] https://cloud.tencent.com/developer/beta/article/1780993
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.