【Laya】HttpRequest 网络请求

Laya.HttpRequest 网络请求使用指南

简介

Laya.HttpRequest 是 LayaAir 引擎提供的 HTTP 网络请求类,封装了原生 XMLHttpRequest 对象,支持 GET、POST、HEAD 等多种 HTTP 方法,可实现与后端服务器的数据交互。

适用场景

  • 向服务器请求 JSON、文本、二进制数据
  • 表单提交和文件上传
  • RESTful API 调用
  • 游戏数据同步、排行榜、用户登录等

工作原理

复制代码
创建请求 → 设置事件监听 → 发送请求 → 接收响应 → 处理数据

核心优势

优势 说明
异步请求 不阻塞主线程,保证游戏流畅运行
多种格式 支持 text、json、xml、arraybuffer 等响应类型
进度监控 支持下载进度监听
事件驱动 基于 EventDispatcher,事件处理灵活

目录


API 参考

构造函数

方法 说明
new Laya.HttpRequest() 创建一个新的 HttpRequest 对象

发送请求

方法 参数 返回值 说明
send(url, data?, method?, responseType?, headers?) url: 地址 data: 发送数据 method: 请求方法 responseType: 响应类型 headers: 请求头 void 发送 HTTP 请求

参数详情

参数 类型 默认值 说明
url string - 请求地址(需遵守同源策略)
data any null 发送的数据
method `"get" "post" "head"`
responseType string "text" 响应类型:"text" / "json" / "xml" / "arraybuffer"
headers string[] null HTTP 请求头,格式:["key", "value"]

属性

属性 类型 只读 说明
url string 请求的地址
data any 返回的数据
http any 原生 XMLHttpRequest 对象引用

方法

方法 返回值 说明
reset() void 重置对象,清除所有事件监听器和数据

事件

事件 说明
Laya.Event.PROGRESS 请求进度变化时触发
Laya.Event.COMPLETE 请求完成时触发
Laya.Event.ERROR 请求失败时触发

基础用法

1. GET 请求(最常用)

typescript 复制代码
// 创建请求对象
let http = new Laya.HttpRequest();

// 监听事件
http.once(Laya.Event.PROGRESS, this, this.onProgress);
http.once(Laya.Event.COMPLETE, this, this.onComplete);
http.once(Laya.Event.ERROR, this, this.onError);

// 发送 GET 请求
http.send("https://api.example.com/user/info?id=123");

// 进度回调
private onProgress(e: any): void {
    console.log("加载进度:", e);
}

// 成功回调
private onComplete(e: any): void {
    console.log("响应数据:", http.data);
}

// 失败回调
private onError(e: any): void {
    console.log("请求失败:", e);
}

2. POST 请求(发送数据)

typescript 复制代码
let http = new Laya.HttpRequest();

http.once(Laya.Event.COMPLETE, this, this.onComplete);
http.once(Laya.Event.ERROR, this, this.onError);

// 发送 POST 请求,数据为键值对格式
let postData = "name=player1&score=100&level=5";
http.send("https://api.example.com/score/submit", postData, "post");

private onComplete(e: any): void {
    let response = JSON.parse(http.data);
    console.log("提交成功:", response);
}

3. 请求 JSON 数据

typescript 复制代码
let http = new Laya.HttpRequest();

http.once(Laya.Event.COMPLETE, this, (e: any) => {
    // 直接使用 JSON.parse 解析
    let data = JSON.parse(http.data);
    console.log("用户名:", data.name);
    console.log("等级:", data.level);
});

http.send("https://api.example.com/config", null, "get", "text");

4. 设置请求头

typescript 复制代码
let http = new Laya.HttpRequest();

http.once(Laya.Event.COMPLETE, this, this.onComplete);

// 设置自定义请求头
let headers = [
    "Content-Type", "application/json",
    "Authorization", "Bearer token123",
    "X-Custom-Header", "custom-value"
];

http.send("https://api.example.com/data", null, "get", "text", headers);

实用示例

示例1: 用户登录验证

typescript 复制代码
@regClass()
export class LoginManager extends Laya.Script {
    private static readonly LOGIN_URL = "https://api.example.com/auth/login";

    /**
     * 发起登录请求
     */
    public login(username: string, password: string): Promise<void> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    let response = JSON.parse(http.data);

                    if (response.code === 200) {
                        // 登录成功,保存 token
                        Laya.LocalStorage.setItem("token", response.data.token);
                        Laya.LocalStorage.setItem("userId", response.data.userId);
                        resolve();
                    } else {
                        reject(new Error(response.message || "登录失败"));
                    }
                } catch (e) {
                    reject(new Error("数据解析失败"));
                }
            });

            http.once(Laya.Event.ERROR, this, (e: any) => {
                reject(new Error("网络请求失败"));
            });

            // 构建请求数据
            let postData = `username=${username}&password=${password}`;
            http.send(LoginManager.LOGIN_URL, postData, "post");
        });
    }

    /**
     * 获取保存的 token
     */
    public getToken(): string | null {
        return Laya.LocalStorage.getItem("token");
    }
}

示例2: 排行榜数据加载

typescript 复制代码
@regClass()
export class RankManager extends Laya.Script {
    private static readonly RANK_URL = "https://api.example.com/rank/list";
    private isLoading: boolean = false;

    /**
     * 加载排行榜数据
     */
    public loadRankList(type: number = 1): Promise<any[]> {
        return new Promise((resolve, reject) => {
            if (this.isLoading) {
                reject(new Error("正在加载中..."));
                return;
            }

            this.isLoading = true;
            let http = new Laya.HttpRequest();

            // 显示加载进度
            http.on(Laya.Event.PROGRESS, this, (e: any) => {
                console.log("排行榜加载进度:", e);
            });

            http.once(Laya.Event.COMPLETE, this, () => {
                this.isLoading = false;
                try {
                    let response = JSON.parse(http.data);

                    if (response.code === 200) {
                        resolve(response.data.list);
                    } else {
                        reject(new Error(response.message));
                    }
                } catch (e) {
                    reject(new Error("数据解析失败"));
                }
            });

            http.once(Laya.Event.ERROR, this, () => {
                this.isLoading = false;
                reject(new Error("网络错误"));
            });

            // 请求排行榜数据
            let url = `${RankManager.RANK_URL}?type=${type}&limit=100`;
            http.send(url, null, "get", "text");
        });
    }
}

示例3: 游戏配置预加载

typescript 复制代码
@regClass()
export class ConfigManager extends Laya.Script {
    private static config: any = null;

    /**
     * 加载游戏配置
     */
    public static loadConfig(): Promise<void> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    ConfigManager.config = JSON.parse(http.data);
                    console.log("配置加载成功:", ConfigManager.config);
                    resolve();
                } catch (e) {
                    reject(new Error("配置解析失败"));
                }
            });

            http.once(Laya.Event.ERROR, this, () => {
                reject(new Error("配置加载失败"));
            });

            http.send("https://cdn.example.com/config/game_config.json", null, "get", "text");
        });
    }

    /**
     * 获取配置项
     */
    public static get(key: string): any {
        return ConfigManager.config?.[key];
    }

    /**
     * 游戏启动时加载配置
     */
    public static async init(): Promise<void> {
        await ConfigManager.loadConfig();
        console.log("服务器地址:", ConfigManager.get("serverUrl"));
        console.log("游戏版本:", ConfigManager.get("version"));
    }
}

示例4: 文件上传(multipart/form-data)

typescript 复制代码
@regClass()
export class UploadManager extends Laya.Script {
    /**
     * 上传玩家头像
     */
    public uploadAvatar(imageData: ArrayBuffer): Promise<string> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    let response = JSON.parse(http.data);

                    if (response.code === 200) {
                        resolve(response.data.url);
                    } else {
                        reject(new Error(response.message));
                    }
                } catch (e) {
                    reject(new Error("上传失败"));
                }
            });

            http.once(Laya.Event.ERROR, this, () => {
                reject(new Error("网络错误"));
            });

            // 设置上传请求头
            let token = Laya.LocalStorage.getItem("token");
            let headers = [
                "Authorization", `Bearer ${token}`
            ];

            // 发送上传请求(需要后端支持)
            http.send("https://api.example.com/user/avatar", imageData, "post", "arraybuffer", headers);
        });
    }
}

示例5: 带重试机制的请求

typescript 复制代码
@regClass()
export class RetryHttpClient extends Laya.Script {
    private static readonly MAX_RETRY = 3;
    private static readonly RETRY_DELAY = 1000;

    /**
     * 带重试的 HTTP 请求
     */
    public static request(url: string, data: any = null, method: string = "get", retryCount: number = 0): Promise<any> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    let response = JSON.parse(http.data);

                    if (response.code === 200) {
                        resolve(response.data);
                    } else {
                        // 业务错误,不重试
                        reject(new Error(response.message));
                    }
                } catch (e) {
                    // JSON 解析失败,可能是其他格式
                    resolve(http.data);
                }
            });

            http.once(Laya.Event.ERROR, this, () => {
                if (retryCount < this.MAX_RETRY) {
                    console.log(`请求失败,${this.RETRY_DELAY}ms 后进行第 ${retryCount + 1} 次重试...`);

                    // 延迟重试
                    Laya.timer.once(this.RETRY_DELAY, this, () => {
                        this.request(url, data, method, retryCount + 1)
                            .then(resolve)
                            .catch(reject);
                    });
                } else {
                    reject(new Error("请求失败,已达最大重试次数"));
                }
            });

            http.send(url, data, method, "text");
        });
    }

    /**
     * 使用示例
     */
    public static fetchData(): void {
        this.request("https://api.example.com/data")
            .then(data => {
                console.log("请求成功:", data);
            })
            .catch(err => {
                console.error("请求失败:", err.message);
            });
    }
}

示例6: 请求队列管理

typescript 复制代码
@regClass()
export class RequestQueue extends Laya.Script {
    private static queue: Array<() => Promise<any>> = [];
    private static isProcessing: boolean = false;
    private static readonly MAX_CONCURRENT = 3;
    private static currentCount: number = 0;

    /**
     * 添加请求到队列
     */
    public static add(requestFn: () => Promise<any>): Promise<any> {
        return new Promise((resolve, reject) => {
            this.queue.push(async () => {
                try {
                    let result = await requestFn();
                    resolve(result);
                } catch (e) {
                    reject(e);
                }
            });
            this.processQueue();
        });
    }

    /**
     * 处理队列
     */
    private static processQueue(): void {
        if (this.isProcessing || this.queue.length === 0 || this.currentCount >= this.MAX_CONCURRENT) {
            return;
        }

        this.isProcessing = true;

        while (this.queue.length > 0 && this.currentCount < this.MAX_CONCURRENT) {
            this.currentCount++;
            let requestFn = this.queue.shift()();

            requestFn().finally(() => {
                this.currentCount--;
                this.processQueue();
            });
        }

        this.isProcessing = false;
    }

    /**
     * 使用示例:批量加载资源
     */
    public static loadBatchResources(urls: string[]): Promise<any[]> {
        let promises = urls.map(url => {
            return this.add(() => {
                return new Promise((resolve, reject) => {
                    let http = new Laya.HttpRequest();
                    http.once(Laya.Event.COMPLETE, this, () => resolve(http.data));
                    http.once(Laya.Event.ERROR, this, () => reject(new Error(`加载失败: ${url}`)));
                    http.send(url);
                });
            });
        });

        return Promise.all(promises);
    }
}

高级技巧

1. 使用 Promise 封装

typescript 复制代码
class HttpClient {
    /**
     * GET 请求
     */
    public static get(url: string, headers?: string[]): Promise<any> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    resolve(JSON.parse(http.data));
                } catch (e) {
                    resolve(http.data);
                }
            });

            http.once(Laya.Event.ERROR, this, (e: any) => {
                reject(e);
            });

            http.send(url, null, "get", "text", headers);
        });
    }

    /**
     * POST 请求
     */
    public static post(url: string, data: any, headers?: string[]): Promise<any> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    resolve(JSON.parse(http.data));
                } catch (e) {
                    resolve(http.data);
                }
            });

            http.once(Laya.Event.ERROR, this, (e: any) => {
                reject(e);
            });

            http.send(url, data, "post", "text", headers);
        });
    }
}

// 使用示例
HttpClient.get("https://api.example.com/data")
    .then(data => console.log(data))
    .catch(err => console.error(err));

2. 请求超时处理

typescript 复制代码
class TimeoutHttpClient {
    private static readonly TIMEOUT = 10000; // 10秒超时

    public static requestWithTimeout(url: string, timeout: number = this.TIMEOUT): Promise<any> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();
            let isComplete = false;
            let timer: any;

            // 超时计时器
            timer = Laya.timer.once(timeout, this, () => {
                if (!isComplete) {
                    isComplete = true;
                    http.reset(); // 中断请求
                    reject(new Error("请求超时"));
                }
            });

            http.once(Laya.Event.COMPLETE, this, () => {
                Laya.timer.clear(timer);
                if (!isComplete) {
                    isComplete = true;
                    try {
                        resolve(JSON.parse(http.data));
                    } catch (e) {
                        resolve(http.data);
                    }
                }
            });

            http.once(Laya.Event.ERROR, this, (e: any) => {
                Laya.timer.clear(timer);
                if (!isComplete) {
                    isComplete = true;
                    reject(e);
                }
            });

            http.send(url);
        });
    }
}

3. 请求缓存

typescript 复制代码
class CachedHttpClient {
    private static cache: Map<string, { data: any; timestamp: number }> = new Map();
    private static readonly CACHE_DURATION = 60000; // 缓存1分钟

    /**
     * 带缓存的 GET 请求
     */
    public static getCached(url: string): Promise<any> {
        let now = Date.now();
        let cached = this.cache.get(url);

        if (cached && (now - cached.timestamp) < this.CACHE_DURATION) {
            console.log("使用缓存数据:", url);
            return Promise.resolve(cached.data);
        }

        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    let data = JSON.parse(http.data);
                    // 保存到缓存
                    this.cache.set(url, { data, timestamp: now });
                    resolve(data);
                } catch (e) {
                    resolve(http.data);
                }
            });

            http.once(Laya.Event.ERROR, this, (e: any) => {
                reject(e);
            });

            http.send(url);
        });
    }

    /**
     * 清除缓存
     */
    public static clearCache(url?: string): void {
        if (url) {
            this.cache.delete(url);
        } else {
            this.cache.clear();
        }
    }
}

4. 复用 HttpRequest 对象

typescript 复制代码
@regClass()
export class ReusableHttpClient extends Laya.Script {
    private http: Laya.HttpRequest;
    private onCompleteHandlers: Array<(data: any) => void> = [];
    private onErrorHandlers: Array<(error: any) => void> = [];

    constructor() {
        super();
        this.http = new Laya.HttpRequest();
        this.http.on(Laya.Event.COMPLETE, this, this.onComplete);
        this.http.on(Laya.Event.ERROR, this, this.onError);
    }

    /**
     * 发送请求
     */
    public send(url: string, data?: any, method?: string): Promise<any> {
        return new Promise((resolve, reject) => {
            this.onCompleteHandlers.push(resolve);
            this.onErrorHandlers.push(reject);
            this.http.send(url, data, method);
        });
    }

    private onComplete(): void {
        let handlers = this.onCompleteHandlers;
        this.onCompleteHandlers = [];
        handlers.forEach(fn => fn(this.http.data));
    }

    private onError(e: any): void {
        let handlers = this.onErrorHandlers;
        this.onErrorHandlers = [];
        handlers.forEach(fn => fn(e));
    }

    onDestroy(): void {
        this.http.reset();
        this.http.offAll();
    }
}

最佳实践

1. 每次请求创建新对象

typescript 复制代码
// ✅ 推荐:每次请求创建新对象
let http1 = new Laya.HttpRequest();
http1.send(url1);

let http2 = new Laya.HttpRequest();
http2.send(url2);

// ❌ 不推荐:复用同一对象可能导致数据混乱
let http = new Laya.HttpRequest();
http.send(url1);
http.send(url2); // 可能会清除第一个请求的数据

2. 使用 once 而非 on 监听事件

typescript 复制代码
// ✅ 推荐:使用 once,自动移除监听
http.once(Laya.Event.COMPLETE, this, this.onComplete);

// ❌ 不推荐:使用 on,需要手动移除
http.on(Laya.Event.COMPLETE, this, this.onComplete);
// 之后需要手动 off 移除监听

3. 统一的错误处理

typescript 复制代码
class ApiClient {
    private static readonly BASE_URL = "https://api.example.com";

    /**
     * 统一请求方法
     */
    private static request(endpoint: string, data?: any, method?: string, headers?: string[]): Promise<any> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();

            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    let response = JSON.parse(http.data);

                    // 统一处理业务状态码
                    switch (response.code) {
                        case 200:
                            resolve(response.data);
                            break;
                        case 401:
                            // token 过期,重新登录
                            this.handleTokenExpired();
                            reject(new Error("登录已过期"));
                            break;
                        case 403:
                            reject(new Error("无权限访问"));
                            break;
                        default:
                            reject(new Error(response.message || "请求失败"));
                    }
                } catch (e) {
                    reject(new Error("数据解析失败"));
                }
            });

            http.once(Laya.Event.ERROR, this, (e: any) => {
                reject(new Error("网络连接失败"));
            });

            let url = `${this.BASE_URL}${endpoint}`;
            http.send(url, data, method, "text", headers);
        });
    }

    private static handleTokenExpired(): void {
        // 清除登录状态
        Laya.LocalStorage.setItem("token", "");
        // 跳转到登录页
        Laya.Scene.open("scenes/LoginScene.scene");
    }
}

4. 添加请求签名(安全)

typescript 复制代码
class SecureApiClient {
    private static readonly APP_KEY = "your_app_key";
    private static readonly APP_SECRET = "your_app_secret";

    /**
     * 生成签名
     */
    private static sign(params: Record<string, any>): string {
        // 按字母顺序排序
        let sortedKeys = Object.keys(params).sort();
        let signStr = sortedKeys.map(key => `${key}=${params[key]}`).join("&");
        signStr += `&app_secret=${this.APP_SECRET}`;
        return Laya.Utils.toMD5(signStr);
    }

    /**
     * 发起带签名的请求
     */
    public static signedRequest(endpoint: string, params: Record<string, any>): Promise<any> {
        // 添加公共参数
        params.app_key = this.APP_KEY;
        params.timestamp = Date.now();
        params.nonce = Math.random().toString(36).substring(2);

        // 生成签名
        params.sign = this.sign(params);

        // 构建查询字符串
        let queryString = Object.keys(params)
            .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
            .join("&");

        let http = new Laya.HttpRequest();
        return new Promise((resolve, reject) => {
            http.once(Laya.Event.COMPLETE, this, () => {
                try {
                    let response = JSON.parse(http.data);
                    resolve(response.data);
                } catch (e) {
                    reject(new Error("数据解析失败"));
                }
            });

            http.once(Laya.Event.ERROR, this, () => {
                reject(new Error("请求失败"));
            });

            http.send(`${endpoint}?${queryString}`);
        });
    }
}

5. 场景切换时取消请求

typescript 复制代码
@regClass()
export class GameScene extends Laya.Scene {
    private activeRequests: Laya.HttpRequest[] = [];

    /**
     * 发送可追踪的请求
     */
    public trackedRequest(url: string): Promise<any> {
        return new Promise((resolve, reject) => {
            let http = new Laya.HttpRequest();
            this.activeRequests.push(http);

            http.once(Laya.Event.COMPLETE, this, () => {
                // 从列表中移除
                let index = this.activeRequests.indexOf(http);
                if (index > -1) this.activeRequests.splice(index, 1);

                try {
                    resolve(JSON.parse(http.data));
                } catch (e) {
                    resolve(http.data);
                }
            });

            http.once(Laya.Event.ERROR, this, () => {
                let index = this.activeRequests.indexOf(http);
                if (index > -1) this.activeRequests.splice(index, 1);
                reject(new Error("请求失败"));
            });

            http.send(url);
        });
    }

    /**
     * 场景销毁时取消所有请求
     */
    onDestroy(): void {
        // 取消所有活跃的请求
        this.activeRequests.forEach(http => {
            http.reset();
        });
        this.activeRequests = [];
    }
}

注意事项

  1. 同源策略:浏览器的同源安全策略要求请求 URL 与脚本具有相同的主机名和端口
  2. 每次新建对象 :建议每次请求使用新的 HttpRequest 对象,避免数据混乱
  3. 使用 once 监听 :推荐使用 once() 而非 on() 监听事件,自动移除避免内存泄漏
  4. 错误处理 :始终监听 Event.ERROR 事件处理请求失败情况
  5. HTTPS 通信:生产环境建议使用 HTTPS 确保数据传输安全
  6. 数据验证:接收数据后验证格式和内容,避免恶意数据导致游戏异常

相关文档

相关推荐
meng半颗糖2 小时前
vue3+typeScript 在线预览 excel,word,pdf
typescript·word·excel
游戏开发爱好者82 小时前
iPhone 网络调试的过程,请求是否发出,是否经过系统代理,app 绕过代理获取数据
android·网络·ios·小程序·uni-app·iphone·webview
我不是程序员yy2 小时前
零基础入门:什么是网关?它在互联网通信中扮演什么角色
网络·智能路由器
每次学一点2 小时前
如何将网吧电脑加入ZeroTier虚拟局域网
运维·服务器·网络
程序员储物箱2 小时前
如何通过静态路由实现电脑同时连通内外网?!
网络
Anthony_2312 小时前
三、路由基础与静态路由
网络·网络协议·tcp/ip·http·udp·智能路由器
勉灬之2 小时前
基于 Node.js + mysql2 的实用同步助手,适合开发/测试环境下快速对齐表数据
服务器·网络·node.js
一路向北he2 小时前
ac791 wifi连接成功流程
网络·智能路由器
Mr Aokey2 小时前
RabbitMQ进阶实战:三种典型消息路由模式详解(订阅/路由/主题)
java·网络·rabbitmq