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