Skip to content

并发请求处理

多个网络请求的并发策略:Promise.all、并发控制、请求合并。


1. 并发策略

1.1 Promise.all — 全部并发

typescript
// 并行请求(所有请求同时发起)
async function loadDashboard() {
    try {
        let [users, posts, comments, stats] = await Promise.all([
            httpClient.request('/api/users', { method: http.HttpRequestMethod.GET }).then(r => r.result),
            httpClient.request('/api/posts', { method: http.HttpRequestMethod.GET }).then(r => r.result),
            httpClient.request('/api/comments', { method: http.HttpRequestMethod.GET }).then(r => r.result),
            httpClient.request('/api/stats', { method: http.HttpRequestMethod.GET }).then(r => r.result)
        ])
        
        return {
            users: JSON.parse(users as string),
            posts: JSON.parse(posts as string),
            comments: JSON.parse(comments as string),
            stats: JSON.parse(stats as string)
        }
    } catch (err) {
        console.error('并发请求失败:', err)
    }
}

// 总时间 = 最慢的那个请求的时间

1.2 Promise.allSettled — 部分失败不中断

typescript
// 所有请求都执行完,无论成败
async function loadAllData() {
    let results = await Promise.allSettled([
        httpClient.request('/api/users', { method: http.HttpRequestMethod.GET }),
        httpClient.request('/api/posts', { method: http.HttpRequestMethod.GET }),
        httpClient.request('/api/comments', { method: http.HttpRequestMethod.GET })
    ])
    
    for (let i = 0; i < results.length; i++) {
        if (results[i].status === 'fulfilled') {
            console.log(`请求 ${i} 成功`)
        } else {
            console.log(`请求 ${i} 失败:`, results[i].reason)
        }
    }
}

1.3 串行请求(依赖关系)

typescript
// 串行请求(前一个完成后再发起下一个)
async function loadUserProfile(userId: string) {
    // 先获取用户信息
    let userResp = await httpClient.request(`/api/users/${userId}`, {
        method: http.HttpRequestMethod.GET
    })
    
    let user = JSON.parse(userResp.result as string)
    
    // 再获取用户动态(依赖用户 ID)
    let postsResp = await httpClient.request(`/api/posts?userId=${userId}`, {
        method: http.HttpRequestMethod.GET
    })
    
    return { user, posts: JSON.parse(postsResp.result as string) }
}

2. 并发控制

2.1 并发限制(防止过多请求)

typescript
// 并发控制:限制同时进行的请求数
class ConcurrencyController {
    private maxConcurrency: number = 5
    private activeCount: number = 0
    private queue: Array<() => Promise<any>> = []

    async execute(task: () => Promise<any>): Promise<any> {
        return new Promise((resolve, reject) => {
            let taskWrapper = () => {
                this.activeCount++
                task()
                    .then(resolve)
                    .catch(reject)
                    .finally(() => {
                        this.activeCount--
                        this.nextTask()
                    })
            }

            if (this.activeCount < this.maxConcurrency) {
                this.activeCount++
                taskWrapper()
            } else {
                this.queue.push(taskWrapper)
            }
        })
    }

    private nextTask(): void {
        if (this.queue.length > 0) {
            let next = this.queue.shift()!
            next()
        }
    }
}

// 使用
let controller = new ConcurrencyController()

// 最多 5 个并发请求
for (let i = 0; i < 20; i++) {
    controller.execute(() => httpClient.request(`/api/data/${i}`))
}

3. 请求合并

3.1 请求去重

typescript
// 请求去重:相同 URL 只请求一次
class RequestDeduplicator {
    private pendingRequests: Map<string, Promise<any>> = new Map()

    request(url: string, options: http.HttpRequestOptions): Promise<any> {
        let key = `${options.method}:${url}`
        
        if (this.pendingRequests.has(key)) {
            // 已有相同请求,返回同一个 Promise
            return this.pendingRequests.get(key)!
        }
        
        let promise = httpClient.request(url, options).then(r => r.result)
        
        this.pendingRequests.set(key, promise)
        promise.finally(() => {
            this.pendingRequests.delete(key)
        })
        
        return promise
    }
}

3.2 批量请求

typescript
// 批量请求:将多个小请求合并为一个大请求
class BatchRequester {
    private batchInterval: number = 100  // 批量间隔(毫秒)
    private queue: Array<{ url: string, resolve: Function, reject: Function }> = []

    batchRequest(url: string, options: http.HttpRequestOptions): Promise<any> {
        return new Promise((resolve, reject) => {
            this.queue.push({ url, resolve, reject })
            
            // 等待批量间隔
            if (this.queue.length === 1) {
                setTimeout(() => {
                    this.flushBatch()
                }, this.batchInterval)
            }
        })
    }

    private async flushBatch(): Promise<void> {
        let urls = this.queue.map(item => item.url)
        this.queue = []
        
        // 合并请求
        let allPromises = urls.map(url =>
            httpClient.request(url, { method: http.HttpRequestMethod.GET })
        )
        
        let results = await Promise.all(allPromises)
        
        for (let i = 0; i < this.queue.length; i++) {
            this.queue[i].resolve(results[i].result)
        }
    }
}

4. 请求取消

4.1 取消请求

typescript
// 取消请求
let httpClient = http.createHttp()

// 启动请求
let promise = httpClient.request('https://api.example.com/data', {
    method: http.HttpRequestMethod.GET
})

// 取消请求
httpClient.cancelAll()

// 使用 AbortController(新版)
let controller = new AbortController()
let httpClient2 = http.createHttp()

httpClient2.request('https://api.example.com/data', {
    method: http.HttpRequestMethod.GET,
    signal: controller.signal
}).then((response) => {
    console.log(response.result)
})

// 取消
controller.abort()

5. 面试高频考点

Q1: Promise.all 和 Promise.allSettled 的区别?

回答:Promise.all 任一失败全部中断;Promise.allSettled 所有请求都执行完,无论成败,可以逐个处理结果。

Q2: 如何控制并发数?

回答:用信号量或队列控制并发数,防止过多请求导致服务器压力。限制同时进行的请求数,排队等待。

Q3: 请求去重/合并有什么用?

回答:去重防止重复请求浪费资源;合并减少请求次数,降低网络开销和服务器压力。


🐱 小猫提示:并发处理记住 "Promise.all 并行、并发控制防过载、请求去重防重复、cancelAll 取消"