HarmonyOS鸿蒙Next中封装http请求,无法处理后台接口(result里面包含code,data,msg),统一处理错误信息弹窗(只在封装库显示,UI界面,只处理返回值数据回显)

HarmonyOS鸿蒙Next中封装http请求,无法处理后台接口(result里面包含code,data,msg),统一处理错误信息弹窗(只在封装库显示,UI界面,只处理返回值数据回显) cke_6041.png

login页面调用

import HttpService from '../api/index';
async loginButton() {
  try {
    const data =  await HttpService('/app/login', { username:this.phoneNumber,password:this.phonePsd } as Form,'post')

    console.log('提交成功,返回 data:', data);

  } catch (e) {
    console.error('提交失败:', e);
  }

cke_9594.png

确认接口调用成功,但是在封装统一的/api/index.ets文件中

promptAction.showToast不生效,希望根据result.msg显示信息,并且在login.ets页面中拿到result信息

更多关于HarmonyOS鸿蒙Next中封装http请求,无法处理后台接口(result里面包含code,data,msg),统一处理错误信息弹窗(只在封装库显示,UI界面,只处理返回值数据回显)的实战教程也可以访问 https://www.itying.com/category-93-b0.html

13 回复

推荐一个全局的弹框。这种的就可以实现你的”统一处理错误信息弹窗“,可以在http模块内弹出错误信息,数据你可以再往下传递到页面内。

OpenHarmony三方库中心仓,名字:@lyb/loading-dialog

亲测好用,当然你也可以参考里面实现思路,自定义个自己所需要的弹框。

更多关于HarmonyOS鸿蒙Next中封装http请求,无法处理后台接口(result里面包含code,data,msg),统一处理错误信息弹窗(只在封装库显示,UI界面,只处理返回值数据回显)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


楼主可以参考一下下面的这种封装方式:

if (res.responseCode === 401) {
    //401 token超时
    //删除持久化数据token
    AppStorage.set<string>(TOKEN_KEY, '')
    promptAction.showToast({ message: 'token超时!' })
    //回登录
    router.replaceUrl({
        url: 'pages/Login/LoginPage'
    })
    //返回错误 终止
    return Promise.reject(new Error('token超时!'))
} else if (res.responseCode === 404) {
    promptAction.showToast({ message: '请求地址不正确!' })
    return Promise.reject(new Error('请求地址不正确!'))
} else {
    //指定为字符串,然后再转成一个对象,类型是不明确的要使用泛型,返回第一层+泛型,泛型的定义是一个类和之前的有所差距
    const result = JSON.parse(res.result as string) as ResponsesData<T>
    //再判断返回的状态码进行处理,不是200都是失败
    if (result.code === 200) {
        return result.data as T
    } else {
        promptAction.showToast({ message: '服务器异常!' })
        return Promise.reject(new Error(result.msg))
    }
}

这里处理通用的拦截

import { http } from '@kit.NetworkKit'
// import { TOKEN_KEY } from '../constants'
// import { BASE_URL } from '../constants/url_var'
import { promptAction, router } from '@kit.ArkUI'
// import { ResponsesData } from './request2'
//传输参数token
export const TOKEN_KEY: string = 'token'
//请求网络的基底址
export const BASE_URL: string = 'https://slwl-api.itheima.net/'
// 最外层数据封装类的
export class ResponsesData<T> {
    code: number = 0
    msg: string = ""
    data: T | null = null
}
interface EmptyInterface {}


//Next版本不支持在箭头函数上写纯泛型,
// function requestHttp(url: url地址, method: 请求方法类型,默认为get, data?: 参数类型) : 返回类型是: Promise里面的T类型的数据
async function requestHttp<T>(url: string = '', method: http.RequestMethod = http.RequestMethod.GET, data?: object): Promise<T> {
    //创建一个网络请求
    const httpRequest = http.createHttp()
    //拼接地址
    let urlStr = BASE_URL + url
    //get方法需要自己拼接
    if (method = http.RequestMethod.GET) {
        //如果data里面有值并且里面有对象的时候
        if (data && Object.keys(data).length) {
            urlStr += "?" + Object.keys(data).map(key => {
                if (data[key]) {
                    return `${key}=${data[key]}` // a=1 =>
                }
                return ""
            }).join('&') //['a=1','b=2','c=3']
        }
    }
    //设置请求对象
    let config: http.HttpRequestOptions = {
        //method同名方法赋值,参数名和属性名相同时只需要写一个method等价于method:method
        method,
        //超时时间
        readTimeout: 10000,
        //get的extraData参数在上面处理过了 在这儿不需要再传一遍
        extraData: method === http.RequestMethod.GET ? '' : data || {} as EmptyInterface,
        //响应参数的类型,指定为对象后有BUG,当结果有问题时,项目会直接瘫痪
        // expectDataType: http.HttpDataType.OBJECT,
        //请求头
        header: {
            'Content-Type': 'application/json',
            "Authorization": AppStorage.get(TOKEN_KEY) as string || ''
        }
    }
    //发请求
    try {

        const res = await httpRequest.request(urlStr, config)
        console.log('请求url地址', urlStr)
        //res.responseCode响应状态码,这里的401还会认为是请求成功
        if (res.responseCode === 401) {
            //401 token超时
            //删除持久化数据token
            AppStorage.set<string>(TOKEN_KEY, '')
            promptAction.showToast({ message: 'token超时!' })
            //回登录
            router.replaceUrl({
                url: 'pages/Login/LoginPage'
            })
            //返回错误 终止
            return Promise.reject(new Error('token超时!'))
        } else if (res.responseCode === 404) {
            promptAction.showToast({ message: '请求地址不正确!' })
            return Promise.reject(new Error('请求地址不正确!'))
        } else {
            //指定为字符串,然后再转成一个对象,类型是不明确的要使用泛型,返回第一层+泛型,泛型的定义是一个类和之前的有所差距
            const result = JSON.parse(res.result as string) as ResponsesData<T>
            //再判断返回的状态码进行处理,不是200都是失败
            if (result.code === 200) {
                return result.data as T
            } else {
                promptAction.showToast({ message: '服务器异常!' })
                return Promise.reject(new Error(result.msg))
            }
        }
    } catch (error) {
        promptAction.showToast({ message: error })
        return Promise.reject(error)
    }
    //执行最后销毁请求
    finally {
        //销毁请求请求结束
        httpRequest.destroy()
    }
}

//封装一个静态类的方法出来使用
export class Request {
    static get<T>(url: string, data?: object): Promise<T> {
        return requestHttp<T>(url, http.RequestMethod.GET, data)
    }

    static post<T>(url: string, data?: object): Promise<T> {
        return requestHttp<T>(url, http.RequestMethod.POST, data)
    }

    static put<T>(url: string, data?: object): Promise<T> {
        return requestHttp<T>(url, http.RequestMethod.PUT, data)
    }

    static delete<T>(url: string, data?: object): Promise<T> {
        return requestHttp<T>(url, http.RequestMethod.DELETE, data)
    }
}

小伙伴你好,可以通过封装一个 Toast 工具类,使用 PromptAction 来统一管理 showToastcloseToast 等方法。

详细说明

封装 Toast 工具类统一管理弹窗

方案说明:通过封装一个工具类,使用私有变量管理 PromptAction 实例,统一提供 showToastcloseToast 等方法,便于在项目中统一管理和使用 Toast 提示功能。详细使用指南请参考:PromptAction API 参考

实现步骤

  1. 导入模块

  导入语句

import { UIContext, PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

  2. 封装 Toast 工具类

  代码示例

import { UIContext, PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * Toast 显示选项接口
 */
interface ToastOptions {
  message: string;
  duration?: number;
  bottom?: string | number;
}

/**
 * Toast 工具类
 * 统一管理 showToast 和 closeToast 等方法
 */
class ToastUtil {
  private static instance: ToastUtil;
  private promptAction: PromptAction | null = null;

  /**
   * 获取单例实例
   *
   * @returns ToastUtil 单例实例
   */
  static getInstance(): ToastUtil {
    if (!ToastUtil.instance) {
      ToastUtil.instance = new ToastUtil();
    }
    return ToastUtil.instance;
  }

  /**
   * 设置 UIContext,用于获取 PromptAction 实例
   *
   * @param context UIContext 实例
   */
  setUIContext(context: UIContext): void {
    try {
      this.promptAction = context.getPromptAction();
    } catch (err) {
      const be = err as BusinessError;
      console.error(`获取 PromptAction 失败: code = ${be.code}, message = ${be.message}`);
      this.promptAction = null;
    }
  }

  /**
   * 显示 Toast 提示
   *
   * @param options Toast 显示选项
   * @returns Promise<number> 返回 toastId,用于后续关闭
   */
  async showToast(options: ToastOptions): Promise<number> {
    if (!this.promptAction) {
      console.error('PromptAction 未设置,无法显示 Toast');
      throw new Error('PromptAction 未设置');
    }

    try {
      // API 18+ 推荐使用 openToast,返回 toastId
      const toastId = await this.promptAction.openToast({
        message: options.message,
        duration: options.duration || 2000,
        bottom: options.bottom || '80vp'
      });
      return toastId;
    } catch (err) {
      const be = err as BusinessError;
      console.error(`显示 Toast 失败: code = ${be.code}, message = ${be.message}`);
      throw new Error(err);
    }
  }

  /**
   * 显示简单文本 Toast(兼容旧版本)
   *
   * @param message 提示信息
   * @param duration 显示时长(毫秒),默认 2000ms
   */
  showSimpleToast(message: string, duration: number = 2000): void {
    if (!this.promptAction) {
      console.error('PromptAction 未设置,无法显示 Toast');
      return;
    }

    try {
      // 兼容旧版本 API,使用 showToast(API 18+ 已弃用)
      this.promptAction.showToast({
        message: message,
        duration: duration
      });
    } catch (err) {
      const be = err as BusinessError;
      console.error(`显示 Toast 失败: code = ${be.code}, message = ${be.message}`);
    }
  }

  /**
   * 关闭指定的 Toast
   *
   * @param toastId Toast ID,由 showToast 返回
   */
  closeToast(toastId: number): void {
    if (!this.promptAction) {
      console.error('PromptAction 未设置,无法关闭 Toast');
      return;
    }

    try {
      this.promptAction.closeToast(toastId);
    } catch (err) {
      const be = err as BusinessError;
      console.error(`关闭 Toast 失败: code = ${be.code}, message = ${be.message}`);
    }
  }

  /**
   * 显示成功提示
   *
   * @param message 提示信息
   * @returns Promise<number> 返回 toastId
   */
  async showSuccess(message: string): Promise<number> {
    return this.showToast({
      message: `✓ ${message}`,
      duration: 2000
    });
  }

  /**
   * 显示错误提示
   *
   * @param message 提示信息
   * @returns Promise<number> 返回 toastId
   */
  async showError(message: string): Promise<number> {
    return this.showToast({
      message: `✗ ${message}`,
      duration: 3000
    });
  }

  /**
   * 显示加载提示
   *
   * @param message 提示信息,默认为"加载中..."
   * @returns Promise<number> 返回 toastId,可用于后续关闭
   */
  async showLoading(message: string = '加载中...'): Promise<number> {
    return this.showToast({
      message: message,
      duration: 10000 // 加载提示显示时间较长
    });
  }
}

export default ToastUtil;

  3. 在组件中使用

  代码示例

import ToastUtil from '../utils/ToastUtil';

@ComponentV2
struct ToastTestPage {
  private toastId: number | null = null;

  /**
   * 组件挂载时设置 UIContext
   */
  aboutToAppear(): void {
    const toastUtil = ToastUtil.getInstance();
    toastUtil.setUIContext(this.getUIContext());
  }

  /**
   * 显示简单提示
   */
  showSimpleMessage(): void {
    const toastUtil = ToastUtil.getInstance();
    toastUtil.showSimpleToast('操作成功');
  }

  /**
   * 显示可控制的 Toast
   */
  async showControllableToast(): Promise<void> {
    const toastUtil = ToastUtil.getInstance();
    try {
      // 显示 Toast 并获取 toastId
      this.toastId = await toastUtil.showToast({
        message: '正在处理中...',
        duration: 5000
      });
      console.info('Toast ID:', this.toastId);
    } catch (err) {
      console.error('显示 Toast 失败:', err);
    }
  }

  /**
   * 提前关闭 Toast
   */
  closeCurrentToast(): void {
    if (this.toastId !== null) {
      const toastUtil = ToastUtil.getInstance();
      toastUtil.closeToast(this.toastId);
      this.toastId = null;
    }
  }

  /**
   * 显示成功提示
   */
  async showSuccessMessage(): Promise<void> {
    const toastUtil = ToastUtil.getInstance();
    await toastUtil.showSuccess('保存成功');
  }

  /**
   * 显示错误提示
   */
  async showErrorMessage(): Promise<void> {
    const toastUtil = ToastUtil.getInstance();
    await toastUtil.showError('操作失败,请重试');
  }

  /**
   * 显示加载提示并自动关闭
   */
  async showLoadingAndClose(): Promise<void> {
    const toastUtil = ToastUtil.getInstance();
    const toastId = await toastUtil.showLoading('正在加载数据...');
    
    // 模拟异步操作
    setTimeout(() => {
      toastUtil.closeToast(toastId);
      toastUtil.showSuccess('加载完成');
    }, 2000);
  }

  build() {
    Column() {
      Button('显示简单提示')
        .onClick(() => {
          this.showSimpleMessage();
        })
        .margin({ bottom: 10 })

      Button('显示可控制 Toast')
        .onClick(() => {
          this.showControllableToast();
        })
        .margin({ bottom: 10 })

      Button('关闭当前 Toast')
        .onClick(() => {
          this.closeCurrentToast();
        })
        .margin({ bottom: 10 })

      Button('显示成功提示')
        .onClick(() => {
          this.showSuccessMessage();
        })
        .margin({ bottom: 10 })

      Button('显示错误提示')
        .onClick(() => {
          this.showErrorMessage();
        })
        .margin({ bottom: 10 })

      Button('显示加载提示')
        .onClick(() => {
          this.showLoadingAndClose();
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

注意事项

  • UIContext 设置:必须在组件中调用 setUIContext() 设置 UIContext,否则无法获取 PromptAction 实例。建议在 aboutToAppear() 生命周期中设置。
  • API 版本兼容性
    • openToastcloseToast 方法需要 API version 18+
    • showToast 方法在 API 18+ 中已弃用,但为了兼容旧版本,工具类中提供了 showSimpleToast 方法
    • 建议优先使用 showToast 方法(内部使用 openToast),可以获取 toastId 进行控制
  • Toast ID 管理
    • showToast 返回的 toastId 需要保存,才能通过 closeToast 关闭
    • 如果不需要手动关闭,可以使用 showSimpleToast 方法
    • 建议在组件中保存 toastId,在组件销毁时关闭未关闭的 Toast
  • 错误处理
    • 所有方法都包含错误处理,失败时会输出错误日志
    • showToast 方法会抛出异常,调用时需要处理异常
    • showSimpleToastcloseToast 方法失败时只输出日志,不会抛出异常
  • 使用场景
    • 简单提示:使用 showSimpleToast 或便捷方法 showSuccessshowError
    • 需要控制关闭:使用 showToast 获取 toastId,然后调用 closeToast
    • 加载提示:使用 showLoading 显示长时间提示,操作完成后调用 closeToast 关闭

参考文档

如何解决“无法连接到远程服务器”错误

问题描述

当尝试连接到远程服务器时,可能会遇到“无法连接到远程服务器”的错误。此错误通常表示客户端无法与目标服务器建立网络连接。

常见原因

  1. 网络连接问题:本地网络不稳定或中断。
  2. 服务器故障:远程服务器已关机、崩溃或未运行所需服务。
  3. 防火墙阻止:本地防火墙或服务器防火墙阻止了连接。
  4. IP地址或端口错误:使用了错误的服务器地址或端口号。
  5. DNS解析失败:域名无法正确解析为IP地址。
  6. 连接超时:服务器响应时间过长,超过了客户端的等待时间。

解决方法

1. 检查本地网络连接

  • 确保您的设备已连接到互联网。
  • 尝试访问其他网站或服务,以确认网络连通性。
  • 重启路由器或调制解调器。

2. 验证服务器状态

  • 确认远程服务器已开机并正常运行。
  • 检查服务器上所需的服务(如Web服务器、数据库等)是否已启动。
  • 联系服务器管理员确认是否存在维护或故障。

3. 检查防火墙设置

  • 本地防火墙:暂时禁用本地防火墙或安全软件,测试连接是否恢复。如果恢复,需在防火墙中添加允许规则。
  • 服务器防火墙:确保服务器防火墙允许来自您IP地址的特定端口连接。

4. 确认IP地址和端口

  • 仔细检查您使用的服务器IP地址和端口号是否正确。
  • 对于Web服务,默认端口通常是80(HTTP)或443(HTTPS)。

5. 排查DNS问题

  • 尝试使用服务器的IP地址直接连接,而非域名。
  • 如果使用IP地址可以连接,则问题可能出在DNS解析上。可以尝试刷新本地DNS缓存或更换DNS服务器。

6. 调整超时设置

  • 如果服务器响应较慢,可以尝试增加客户端的连接超时时间。

7. 使用网络诊断工具

  • Ping:测试与服务器的基本连通性。
    ping 服务器IP地址
    
  • Telnet:测试特定端口是否开放。
    telnet 服务器IP地址 端口号
    
  • Traceroute:跟踪数据包路径,查找网络阻塞点。
    tracert 服务器IP地址 (Windows)
    traceroute 服务器IP地址 (Linux/macOS)
    

8. 检查路由和中间设备

  • 确保本地网络和服务器网络之间的所有路由器、交换机等网络设备工作正常。

预防措施

  • 定期维护服务器,确保系统和应用更新。
  • 监控服务器和网络设备的状态。
  • 配置正确的防火墙规则,并定期审查。
  • 为关键服务设置冗余和负载均衡。

总结

“无法连接到远程服务器”错误通常由网络或服务器端问题引起。通过系统性地检查网络连接、服务器状态、防火墙设置和配置信息,大多数情况下可以定位并解决问题。如果问题持续存在,建议联系网络管理员或服务器提供商寻求进一步帮助。

有没有符合你的要求。有帮到你的话,给我文章点个小小赞👍,

很好,组件中使用toast没问题,但是,封装库中,依然是无法使用,还是报UI获取错误,无法获取上下文,10003。

api调整了

this.getUIContext().getPromptAction().showToast({
    message: '操作成功',  // 提示内容
    duration: 1000       // 显示1秒
  });

在api/index.ets也就是request请求.then返回体中,this.getUIContext()报错,

调用请求封装时,传下context试试,

意思是页面请求的时候,在页面获取到this.getUIContext()的结果,然后随参数传到请求封装里面去。

在鸿蒙Next中封装HTTP请求,可通过拦截器统一处理错误信息。在拦截器中检查响应result中的code字段,若非成功状态,则直接在此处调用弹窗组件显示msg内容。UI界面仅接收处理后的data数据,不涉及错误处理逻辑。需使用ArkTS/TypeScript编写,避免引入Java或C语言相关代码。

根据你的需求,要实现统一错误处理并在UI层只处理业务数据,可以这样设计:

1. 封装HTTP请求层(api/index.ets):

import http from '@ohos.net.http';
import promptAction from '@ohos.promptAction';

class HttpService {
  static async request(url: string, params: any, method: string = 'post'): Promise<any> {
    const httpRequest = http.createHttp();
    
    try {
      const response = await httpRequest.request(url, {
        method: http.RequestMethod[method.toUpperCase()],
        header: { 'Content-Type': 'application/json' },
        extraData: JSON.stringify(params)
      });

      const result = JSON.parse(response.result as string);
      
      // 统一错误处理
      if (result.code !== 200) {
        // 在封装层显示错误提示
        promptAction.showToast({
          message: result.msg || '请求失败',
          duration: 3000
        });
        // 抛出错误,让UI层捕获
        throw new Error(JSON.stringify(result));
      }
      
      // 只返回业务数据给UI层
      return result.data;
      
    } catch (error) {
      // 网络错误等异常处理
      promptAction.showToast({
        message: '网络请求失败',
        duration: 3000
      });
      throw error;
    } finally {
      httpRequest.destroy();
    }
  }
}

export default HttpService.request;

2. UI层使用(login.ets):

async loginButton() {
  try {
    const data = await HttpService('/app/login', {
      username: this.phoneNumber,
      password: this.phonePsd
    }, 'post');
    
    // 这里直接拿到业务数据,无需处理错误信息
    console.log('登录成功,业务数据:', data);
    
    // 处理业务逻辑...
    
  } catch (error) {
    // 这里捕获的是业务错误,可以获取完整result信息
    console.error('业务错误:', error.message);
    const result = JSON.parse(error.message);
    // 可以在这里处理特定的业务逻辑,但不需要显示错误弹窗
  }
}

关键点说明:

  1. 关注点分离:封装层负责统一错误提示和数据处理,UI层只关注业务逻辑
  2. 错误传递:通过throw将完整的result信息传递给UI层,便于特殊处理
  3. 数据净化:封装层只返回result.data,UI层获得干净的业务数据
  4. 弹窗控制:所有错误提示都在封装层完成,UI层无需重复处理

这种设计符合HarmonyOS Next的开发规范,实现了代码的解耦和复用。

回到顶部