4 Commits ee6ab8963a ... 6537358f84

Tác giả SHA1 Thông báo Ngày
  徐勤民 6537358f84 chore(deps): 移除项目中的锁文件并更新依赖版本 5 ngày trước cách đây
  徐勤民 684033a547 feat(sdk_ywx): 更新sdk版本并调整网络请求并发策略 6 ngày trước cách đây
  徐勤民 feafe3a382 refactor(http): 优化HTTP请求管理机制 1 tuần trước cách đây
  徐勤民 92fd9cdd0b fix(http): 修复HttpHelper并发列表操作和请求处理器管理中的异常 1 tuần trước cách đây
4 tập tin đã thay đổi với 329 bổ sung342 xóa
  1. 5 3
      CHANGELOG.md
  2. 0 211
      UTILS.md
  3. 1 1
      oh-package.json5
  4. 323 127
      src/main/ets/http/HttpHelper.ets

+ 5 - 3
CHANGELOG.md

@@ -1,7 +1,9 @@
-# [v1.0.12] 2025.xx.xx
+# [v1.0.12] 2026.01.22
 
-> - 添加一个图片组件`AutoImage`,高度固定,宽度只适应
-> - 'RefreshView'组件,添加一个`canLoadMore`字段,主动控制加载更多
+> - 添加一个图片组件`AutoImage`,高度固定,宽度自适应
+> - `RefreshView`组件,添加一个`canLoadMore`字段,主动控制加载更多
+> - 替换一些过期api
+> - 调整网络工具,不在白名单里面的请求,并发只会请求最后一个,前面几次请求会等待最后一个完成,返回相同的数据
 >
 
 # [v1.0.11] 2025.09.02

+ 0 - 211
UTILS.md

@@ -1,211 +0,0 @@
-- # [@State装饰器:组件内状态](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-0000001774279614)
-> @State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
->
-> 在状态变量相关装饰器中,@State是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。
-
-> @State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
->
-> @State装饰的变量拥有以下特点:
->
-> @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link、@ObjectLink装饰变量之间建立双向数据同步。
->
-> @State装饰的变量生命周期与其所属自定义组件的生命周期相同。
->
-
-- # [@Prop装饰器:父子单向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop-0000001774119942)
-> @Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
-
->@Prop装饰的变量和父组件建立单向的同步关系:
->
->@Prop变量允许在本地修改,但修改后的变化不会同步回父组件。
->
->当数据源更改时,@Prop装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。
-> 
-- # [@Link装饰器:父子双向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link-0000001820999565)
-
-> 子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
->
-> @Link装饰的变量与其父组件中的数据源共享相同的值。
->
-> @Link装饰器不能在@Entry装饰的自定义组件中使用。
-
-- # [@Provide装饰器和@Consume装饰器:与后代组件双向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume-0000001820879589)
-
-> @Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
->
-> 其中@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。
-
-> @Provide/@Consume装饰的状态变量有以下特性:
->
-> @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
->
-> 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
->
-> @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
-
-```typescript
-// 通过相同的变量名绑定
-@Provide a: number = 0;
-@Consume a: number;
-
-// 通过相同的变量别名绑定
-@Provide('a') b: number = 0;
-@Consume('a') c: number;
-@Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide装饰的变量和@Consume装饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量,@Provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@Provide装饰的变量,会发生运行时报错。
-
-```
-
-- # [@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink-0000001774279618)
-> 上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。
-
-> @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
->
-> 被@Observed装饰的类,可以被观察到属性的变化;
->
-> 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
->
-> 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
-
-> 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
->
-> @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
-
-
-
-
-
-- # [@Watch装饰器:状态变量更改通知](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch-0000001774119954)
-> @Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。
-
-> @Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。
-
-```typescript
-@Component
-struct TotalView {
-  @Prop @Watch('onCountUpdated') count: number = 0;
-  @State total: number = 0;
-  // @Watch 回调
-  onCountUpdated(propName: string): void {
-    this.total += this.count;
-  }
-
-  build() {
-    Text(`Total: ${this.total}`)
-  }
-}
-
-@Entry
-@Component
-struct CountModifier {
-  @State count: number = 0;
-
-  build() {
-    Column() {
-      Button('add to basket')
-        .onClick(() => {
-          this.count++
-        })
-      TotalView({ count: this.count })
-    }
-  }
-}
-```
-
-
-
-- # [$$语法:内置组件双向同步](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-two-way-sync-0000001820999577)
-
-```typescript
-// xxx.ets
-@Entry
-@Component
-struct TextInputExample {
-  @State text: string = ''
-  controller: TextInputController = new TextInputController()
-
-  build() {
-    Column({ space: 20 }) {
-      Text(this.text)
-      TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
-        .placeholderColor(Color.Grey)
-        .placeholderFont({ size: 14, weight: 400 })
-        .caretColor(Color.Blue)
-        .width(300)
-    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
-  }
-}
-```
-
-
-
-- # [@Track装饰器:class对象属性级更新](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-track-0000001820879601)
-
-> @Track应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新。
-
-> @Track是class对象的属性装饰器。当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新;而未被标记的属性不能在UI中使用。
-
-```typescript
-class LogTrack {
-  @Track str1: string;
-  @Track str2: string;
-
-  constructor(str1: string) {
-    this.str1 = str1;
-    this.str2 = 'World';
-  }
-}
-
-class LogNotTrack {
-  str1: string;
-  str2: string;
-
-  constructor(str1: string) {
-    this.str1 = str1;
-    this.str2 = '世界';
-  }
-}
-
-@Entry
-@Component
-struct AddLog {
-  @State logTrack: LogTrack = new LogTrack('Hello');
-  @State logNotTrack: LogNotTrack = new LogNotTrack('你好');
-
-  isRender(index: number) {
-    console.log(`Text ${index} is rendered`);
-    return 50;
-  }
-
-  build() {
-    Row() {
-      Column() {
-        Text(this.logTrack.str1) // UINode1
-          .fontSize(this.isRender(1))
-          .fontWeight(FontWeight.Bold)
-        Text(this.logTrack.str2) // UINode2
-          .fontSize(this.isRender(2))
-          .fontWeight(FontWeight.Bold)
-        Button('change logTrack.str1')
-          .onClick(() => {
-            this.logTrack.str1 = 'Bye';
-          })
-        Text(this.logNotTrack.str1) // UINode3
-          .fontSize(this.isRender(3))
-          .fontWeight(FontWeight.Bold)
-        Text(this.logNotTrack.str2) // UINode4
-          .fontSize(this.isRender(4))
-          .fontWeight(FontWeight.Bold)
-        Button('change logNotTrack.str1')
-          .onClick(() => {
-            this.logNotTrack.str1 = '再见';
-          })
-      }
-      .width('100%')
-    }
-    .height('100%')
-  }
-}
-```
-
-
-

+ 1 - 1
oh-package.json5

@@ -1,6 +1,6 @@
 {
   "name": "@szyx/sdk_base",
-  "version": "1.0.11",
+  "version": "1.0.12",
   "description": "数字医信公司,鸿蒙app开发基础工具。",
   "main": "Index.ets",
   "author": "数字医信",

+ 323 - 127
src/main/ets/http/HttpHelper.ets

@@ -7,6 +7,19 @@ import { HttpHelperX, HttpParamsForm, HttpParamsGet, HttpParamsPost, HttpParamsU
 import { BusinessError } from '@kit.BasicServicesKit';
 import { image } from '@kit.ImageKit';
 
+type AnyResult = object | string | number | boolean | null | undefined;
+
+interface Waiters {
+  resolve: (v: AnyResult) => void;
+  reject: (e: Error) => void;
+}
+
+interface PendingRequest {
+  http?: http.HttpRequest
+  waiters: Array<Waiters>
+  seq: number
+}
+
 
 export class HttpHelper {
   private static instance: HttpHelper | null = null
@@ -24,12 +37,16 @@ export class HttpHelper {
   private httpHandlerList = new HashMap<string, http.HttpRequest>();
   // 并发白名单,这个名单里面的api,重复请求不会取消
   private concurrentList = new ArrayList<string>();
+  private pendingMap = new HashMap<string, PendingRequest>();
+  private seqGen = 0;
 
   constructor() {
-    this.httpHandlerList = new HashMap<string, http.HttpRequest>();
     SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
     SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength, this.httpHandlerList.length)
-    this.concurrentList.clear()
+    try {
+      this.concurrentList.clear()
+    } catch (error) {
+    }
   }
 
   /**
@@ -40,27 +57,41 @@ export class HttpHelper {
     if (!apiNo) {
       return
     }
-    if (this.concurrentList.getIndexOf(apiNo) === -1) {
-      this.concurrentList.add(apiNo)
+    try {
+      if (this.concurrentList.getIndexOf(apiNo) === -1) {
+        this.concurrentList.add(apiNo)
+      }
+    } catch (error) {
     }
   }
+
   public addConcurrents(apiNo: string[]) {
     for (let apiNoElement of apiNo) {
-      if (this.concurrentList.getIndexOf(apiNoElement) === -1) {
-        this.concurrentList.add(apiNoElement)
+      try {
+        if (this.concurrentList.getIndexOf(apiNoElement) === -1) {
+          this.concurrentList.add(apiNoElement)
+        }
+      } catch (error) {
       }
     }
   }
 
   public removeConcurrent(apiNo: string) {
-    if (this.concurrentList.getIndexOf(apiNo) !== -1) {
-      this.concurrentList.remove(apiNo)
+    try {
+      if (this.concurrentList.getIndexOf(apiNo) !== -1) {
+        this.concurrentList.remove(apiNo)
+      }
+    } catch (error) {
     }
   }
+
   public removeConcurrents(apiNo: string[]) {
     for (let apiNoElement of apiNo) {
-      if (this.concurrentList.getIndexOf(apiNoElement) !== -1) {
-        this.concurrentList.remove(apiNoElement)
+      try {
+        if (this.concurrentList.getIndexOf(apiNoElement) !== -1) {
+          this.concurrentList.remove(apiNoElement)
+        }
+      } catch (error) {
       }
     }
   }
@@ -73,19 +104,20 @@ export class HttpHelper {
    * @returns
    */
   public postJson<T>(params: HttpParamsPost, apiNo?: string, showLog?: boolean): Promise<T> {
-
     return new Promise<T>((resolve, reject) => {
 
       let httpRequest = http.createHttp();
-      this.setHandler(apiNo ?? params.url, httpRequest)
-
+      const key = apiNo ?? params.url
+      const pending = this.setHandler(key, httpRequest, {
+        resolve: (v: AnyResult) => resolve(v as T),
+        reject: (e: Error) => reject(e)
+      })
 
       const header = HttpHelperX.getHeaders("application/json;charset=UTF-8", params.headers)
 
       if (showLog) {
         LogHelper.debug(`postJson:${apiNo}\n`, JSON.stringify(params))
       }
-
       httpRequest.request(HttpHelperX.getUrl(params.url, params.query), {
         method: http.RequestMethod.POST,
         connectTimeout: 60000,
@@ -93,65 +125,120 @@ export class HttpHelper {
         header: header,
         extraData: params.data,
         usingCache: false,
-      })
-        .then((data: http.HttpResponse) => {
-          if (showLog) {
-            LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
-            LogHelper.print(data)
-          }
+      }).then((data: http.HttpResponse) => {
+        const latest = this.pendingMap.get(key)
+        if (showLog && (!pending || (latest && latest.seq === pending.seq))) {
+          LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
+          LogHelper.print(data)
+        }
 
-          if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-            this.httpHandlerList.remove(apiNo ?? params.url)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-              this.httpHandlerList.length)
-          }
-          if (data.responseCode === 200) {
-            resolve((typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T)
+        if (data.responseCode === 200) {
+          const result = (typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T
+
+          if (!pending) {
+            resolve(result)
           } else {
-            const err: Error = new Error()
-            err.name = data.responseCode.toString()
-            err.message = '服务异常'
+            if (!latest || latest.seq !== pending.seq) {
+              return
+            }
+            const result1 = result as AnyResult
+            latest.waiters.forEach(w => w.resolve(result1))
+          }
+        } else {
+          const err: Error = new Error()
+          err.name = data.responseCode.toString()
+          err.message = '服务异常'
+          if (!pending) {
             reject(err)
+          } else {
+            if (!latest || latest.seq !== pending.seq) {
+              return
+            }
+            latest.waiters.forEach(w => w.reject(err))
           }
-        }).catch((err: Error) => {
-        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
-        if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-          this.httpHandlerList.remove(apiNo ?? params.url)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-            this.httpHandlerList.length)
         }
+
+      }).catch((err: Error) => {
+
+        let e = err
         if (err.message === 'Failed writing received data to disk/application') {
-          const error: Error = new Error()
-          error.name = 'cancel'
-          error.message = err.message
-          reject(error)
+          e.name = 'cancel'
+          e.message = err.message
+        }
+
+        if (!pending) {
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          reject(e)
         } else {
-          reject(err)
+          const latest = this.pendingMap.get(key)
+          if (!latest || latest.seq !== pending.seq) {
+            return
+          }
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          latest.waiters.forEach(w => w.reject(e))
         }
-      });
-    });
+      }).finally(() => {
 
+        if (pending) {
+          const latest = this.pendingMap.get(key)
+          if (latest && latest.seq === pending.seq) {
+            this.pendingMap.remove(key)
+            this.httpHandlerList.remove(key)
+          }
+        } else {
+          this.httpHandlerList.remove(key)
+        }
+
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
+        SZYXLocalStorageHelper.storage.setOrCreate(
+          SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length
+        )
+      })
+    })
   }
 
-  setHandler(key: string, httpRequest: http.HttpRequest) {
-    if (this.concurrentList.getIndexOf(key) === -1 &&
-    this.httpHandlerList.hasKey(key)) {
-      this.httpHandlerList.get(key).destroy()
-      this.httpHandlerList.remove(key)
-      SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-      SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-        this.httpHandlerList.length)
+  setHandler(key: string, httpRequest: http.HttpRequest, waiters: Waiters): PendingRequest | null {
+    // 白名单:完全绕过
+    if (this.concurrentList.getIndexOf(key) !== -1) {
+      return null
     }
-    if (this.concurrentList.getIndexOf(key) === -1) {
-      this.httpHandlerList.set(key, httpRequest)
-      SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-      SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-        this.httpHandlerList.length)
+    if (this.seqGen > 99999) {
+      this.seqGen = 0
     }
+    const seq = ++this.seqGen
+    let pending = this.pendingMap.get(key)
+
+    if (!pending) {
+      pending = {
+        waiters: [waiters],
+        seq,
+        http: httpRequest
+      }
+      this.pendingMap.set(key, pending)
+    } else {
+      // cancel 旧请求
+      const h = pending.http
+      pending = {
+        waiters: [...pending.waiters, waiters],
+        seq,
+        http: httpRequest
+      }
+      this.pendingMap.set(key, pending)
+      h?.destroy()
+    }
+
+    this.httpHandlerList.set(key, httpRequest)
+
+    SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
+    SZYXLocalStorageHelper.storage.setOrCreate(
+      SZYXLocalStorageKeys.HttpHandlerListLength,
+      this.httpHandlerList.length
+    )
+    return pending
   }
 
+
   /**
    * postForm请求
    * @param url url地址
@@ -164,7 +251,11 @@ export class HttpHelper {
     return new Promise<T>((resolve, reject) => {
 
       let httpRequest = http.createHttp();
-      this.setHandler(apiNo ?? params.url, httpRequest)
+      const key = apiNo ?? params.url
+      const pending = this.setHandler(key, httpRequest, {
+        resolve: (v: AnyResult) => resolve(v as T),
+        reject: (e: Error) => reject(e)
+      })
 
       const header = HttpHelperX.getHeaders("application/x-www-form-urlencoded;charset=UTF-8", params.headers)
       let data = HttpHelperX.getContent(params.data)
@@ -182,41 +273,73 @@ export class HttpHelper {
         extraData: data ? encodeURI(data) : undefined
       })
         .then((data: http.HttpResponse) => {
-          if (showLog) {
+          const latest = this.pendingMap.get(key)
+          if (showLog && (!pending || (latest && latest.seq === pending.seq))) {
             LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
             LogHelper.print(data)
           }
 
-          if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-            this.httpHandlerList.remove(apiNo ?? params.url)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-              this.httpHandlerList.length)
-          }
           if (data.responseCode === 200) {
-            resolve((typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T)
+            const result = (typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T
+
+            if (!pending) {
+              resolve(result)
+            } else {
+              if (!latest || latest.seq !== pending.seq) {
+                return
+              }
+              const result1 = result as AnyResult
+              latest.waiters.forEach(w => w.resolve(result1))
+            }
           } else {
             const err: Error = new Error()
             err.name = data.responseCode.toString()
             err.message = '服务异常'
-            reject(err)
+            if (!pending) {
+              reject(err)
+            } else {
+              if (!latest || latest.seq !== pending.seq) {
+                return
+              }
+              latest.waiters.forEach(w => w.reject(err))
+            }
           }
         }).catch((err: Error) => {
-        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
-        if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-          this.httpHandlerList.remove(apiNo ?? params.url)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-            this.httpHandlerList.length)
-        }
+
+        let e = err
         if (err.message === 'Failed writing received data to disk/application') {
-          const error: Error = new Error()
-          error.name = 'cancel'
-          error.message = err.message
-          reject(error)
+          e.name = 'cancel'
+          e.message = err.message
+        }
+
+        if (!pending) {
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          reject(e)
         } else {
-          reject(err)
+          const latest = this.pendingMap.get(key)
+          if (!latest || latest.seq !== pending.seq) {
+            return
+          }
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          latest.waiters.forEach(w => w.reject(e))
         }
+      }).finally(() => {
+
+        if (pending) {
+          const latest = this.pendingMap.get(key)
+          if (latest && latest.seq === pending.seq) {
+            this.pendingMap.remove(key)
+            this.httpHandlerList.remove(key)
+          }
+        } else {
+          this.httpHandlerList.remove(key)
+        }
+
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
+        SZYXLocalStorageHelper.storage.setOrCreate(
+          SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length
+        )
       });
     });
 
@@ -236,7 +359,11 @@ export class HttpHelper {
     return new Promise<T>((resolve, reject) => {
 
       let httpRequest = http.createHttp();
-      this.setHandler(apiNo ?? params.url, httpRequest)
+      const key = apiNo ?? params.url
+      const pending = this.setHandler(key, httpRequest, {
+        resolve: (v: AnyResult) => resolve(v as T),
+        reject: (e: Error) => reject(e)
+      })
 
       if (showLog) {
         LogHelper.debug(`GET:${apiNo}\n`, HttpHelperX.getUrl(params.url, params.query) + '\n',
@@ -252,46 +379,73 @@ export class HttpHelper {
         // extraData: params.data
       })
         .then((data: http.HttpResponse) => {
-          if (showLog) {
-            LogHelper.debug(`${apiNo}:\n${data.result as string}`)
+          const latest = this.pendingMap.get(key)
+          if (showLog && (!pending || (latest && latest.seq === pending.seq))) {
+            LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
             LogHelper.print(data)
           }
 
-          if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-            this.httpHandlerList.remove(apiNo ?? params.url)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-            SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-              this.httpHandlerList.length)
-          }
           if (data.responseCode === 200) {
+            const result = (typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T
 
-            if (typeof data.result === 'string') {
-              resolve(JSON.parse(data.result) as T)
+            if (!pending) {
+              resolve(result)
             } else {
-              resolve(data.result as T)
+              if (!latest || latest.seq !== pending.seq) {
+                return
+              }
+              const result1 = result as AnyResult
+              latest.waiters.forEach(w => w.resolve(result1))
             }
           } else {
             const err: Error = new Error()
             err.name = data.responseCode.toString()
             err.message = '服务异常'
-            reject(err)
+            if (!pending) {
+              reject(err)
+            } else {
+              if (!latest || latest.seq !== pending.seq) {
+                return
+              }
+              latest.waiters.forEach(w => w.reject(err))
+            }
           }
         }).catch((err: Error) => {
-        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
-        if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-          this.httpHandlerList.remove(apiNo ?? params.url)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-            this.httpHandlerList.length)
-        }
+
+        let e = err
         if (err.message === 'Failed writing received data to disk/application') {
-          const error: Error = new Error()
-          error.name = 'cancel'
-          error.message = err.message
-          reject(error)
+          e.name = 'cancel'
+          e.message = err.message
+        }
+
+        if (!pending) {
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          reject(e)
+        } else {
+          const latest = this.pendingMap.get(key)
+          if (!latest || latest.seq !== pending.seq) {
+            return
+          }
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          latest.waiters.forEach(w => w.reject(e))
+        }
+      }).finally(() => {
+
+        if (pending) {
+          const latest = this.pendingMap.get(key)
+          if (latest && latest.seq === pending.seq) {
+            this.pendingMap.remove(key)
+            this.httpHandlerList.remove(key)
+          }
         } else {
-          reject(err)
+          this.httpHandlerList.remove(key)
         }
+
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
+        SZYXLocalStorageHelper.storage.setOrCreate(
+          SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length
+        )
       });
     });
 
@@ -309,7 +463,11 @@ export class HttpHelper {
     return new Promise<T>((resolve, reject) => {
 
       let httpRequest = http.createHttp();
-      this.setHandler(apiNo ?? params.url, httpRequest)
+      const key = apiNo ?? params.url
+      const pending = this.setHandler(key, httpRequest, {
+        resolve: (v: AnyResult) => resolve(v as T),
+        reject: (e: Error) => reject(e)
+      })
 
       if (showLog) {
         LogHelper.debug(`postJson:${apiNo}\n`, JSON.stringify(params))
@@ -329,37 +487,72 @@ export class HttpHelper {
         multiFormDataList: params.data,
       })
         .then((data: http.HttpResponse) => {
-          if (showLog) {
+          const latest = this.pendingMap.get(key)
+          if (showLog && (!pending || (latest && latest.seq === pending.seq))) {
             LogHelper.debug(`${apiNo}:\n ${data.result as string}`)
             LogHelper.print(data)
           }
           if (data.responseCode === 200) {
-            resolve((typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T)
+            const result = (typeof data.result === 'string' ? JSON.parse(data.result) : data.result) as T
+
+            if (!pending) {
+              resolve(result)
+            } else {
+              if (!latest || latest.seq !== pending.seq) {
+                return
+              }
+              const result1 = result as AnyResult
+              latest.waiters.forEach(w => w.resolve(result1))
+            }
           } else {
             const err: Error = new Error()
             err.name = data.responseCode.toString()
             err.message = '服务异常'
-            reject(err)
+            if (!pending) {
+              reject(err)
+            } else {
+              if (!latest || latest.seq !== pending.seq) {
+                return
+              }
+              latest.waiters.forEach(w => w.reject(err))
+            }
           }
         }).catch((err: Error) => {
 
-        LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+        let e = err
         if (err.message === 'Failed writing received data to disk/application') {
-          const error: Error = new Error()
-          error.name = 'cancel'
-          error.message = err.message
-          reject(error)
+          e.name = 'cancel'
+          e.message = err.message
+        }
+
+        if (!pending) {
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          reject(e)
         } else {
-          reject(err)
+          const latest = this.pendingMap.get(key)
+          if (!latest || latest.seq !== pending.seq) {
+            return
+          }
+          LogHelper.error(JSON.stringify({ err: err, url: params.url, }))
+          latest.waiters.forEach(w => w.reject(e))
         }
       }).finally(() => {
-        httpRequest.off("dataSendProgress");
-        if (this.httpHandlerList.hasKey(apiNo ?? params.url)) {
-          this.httpHandlerList.remove(apiNo ?? params.url)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-          SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-            this.httpHandlerList.length)
+
+        if (pending) {
+          const latest = this.pendingMap.get(key)
+          if (latest && latest.seq === pending.seq) {
+            this.pendingMap.remove(key)
+            this.httpHandlerList.remove(key)
+          }
+        } else {
+          this.httpHandlerList.remove(key)
         }
+
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
+        SZYXLocalStorageHelper.storage.setOrCreate(
+          SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length
+        )
       });
     });
 
@@ -373,12 +566,15 @@ export class HttpHelper {
   }
 
   cancel(apiNo: string) {
-    if (this.httpHandlerList.hasKey(apiNo)) {
-      this.httpHandlerList.get(apiNo).destroy()
-      this.httpHandlerList.remove(apiNo)
-      SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
-      SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
-        this.httpHandlerList.length)
+    try {
+      if (this.httpHandlerList.hasKey(apiNo)) {
+        this.httpHandlerList.get(apiNo).destroy()
+        this.httpHandlerList.remove(apiNo)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerList, this.httpHandlerList)
+        SZYXLocalStorageHelper.storage.setOrCreate(SZYXLocalStorageKeys.HttpHandlerListLength,
+          this.httpHandlerList.length)
+      }
+    } catch (error) {
     }
   }