关于javascript:你说你会Promise那你解决一下项目中的这五个难题

11次阅读

共计 5719 个字符,预计需要花费 15 分钟才能阅读完成。

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点 是我的座右铭,根底是进阶的前提 是我的初心,家喻户晓哈, Promise 在咱们的开发中是相当的重要,我感觉对于 Promise 的应用等级,能够分为三个等级

  • 1、把握 Promise 的根本应用
  • 2、把握 Promise 的基本原理
  • 3、在我的项目中能灵活运用 Promise 解决一些问题

第一点的话,其实就是能把握 Promise 的一些根本应用办法以及一些办法,如 then、catch、all、race、finally、allSettled、any、resolve 等等

第二点的话,就是要能简略实现一下 Promise 的原理,这能使咱们对 Promise 的那些罕用办法有更好的了解

第三点的话,就是要能灵便 Promise 解决咱们开发中的一些问题,明天我就给大家说一下我用 Promise 在我的项目开发中解决了什么问题吧!

接口申请超时

顾名思义,就是给定一个工夫,如果接口申请超过这个工夫的话就报错

1、本人实现

实现思路就是: 接口申请 延时函数 赛跑,并应用一个 Promise 包着,因为 Promise 的状态是不可逆的,所以如果 接口申请 先跑完则阐明 未超时 Promise 的状态是 fulfilled ,反之, 延时函数 先跑完则阐明 超时了 Promise 的状态是 rejetced ,最初依据 Promise 的状态来判断有无超时

/**
 * 模仿延时
 * @param {number} delay 延迟时间
 * @returns {Promise<any>}
 */
function sleep(delay) {return new Promise((_, reject) => {setTimeout(() => reject('超时喽'), delay)
  })
}

/**
 * 模仿申请
 */
function request() {
  // 假如申请须要 1s
  return new Promise(resolve => {setTimeout(() => resolve('胜利喽'), 1000)
  })
}

/**
 * 判断是否超时
 * @param {() => Promise<any>} requestFn 申请函数
 * @param {number} delay 提早时长
 * @returns {Promise<any>}
 */
function timeoutPromise(requestFn, delay) {return new Promise((resolve, reject) => {const promises = [requestFn(), sleep(delay)]
    for (const promise of promises) {
      // 超时则执行失败,不超时则执行胜利
      promise.then(res => resolve(res), err => reject(err))
    }
  })
}

2、Promise.race

其实 timeoutPromise 中的代码能够应用 Promise.race 来代替,是同样的成果

function timeoutPromise(requestFn, delay) {
   // 如果先返回的是提早 Promise 则阐明超时了
   return Promise.race([requestFn(), sleep(delay)])
}

3、测试

// 超时
timeoutPromise(request, 500).catch(err => console.log(err)) // 超时喽

// 不超时
timeoutPromise(request, 2000).then(res => console.log(res)) // 胜利喽

转盘抽奖

咱们平时在转盘抽奖时,个别都是开始转动的同时也发动接口申请,所以有两种可能

  • 1、转盘转完,接口还没申请回来,这是不失常的
  • 2、转盘转完前,接口就申请结束,这是失常的,然而须要保障 申请回调 转盘转完回调 同时执行

1、转盘转完,接口还没申请回来

次要问题就是,怎么判断 接口申请工夫 是否超过 转盘转完所需工夫 ,咱们其实能够用到上一个知识点 接口申请超时 ,都是一样的情理。如果 转盘转完所需工夫 2500ms ,那咱们能够限定 接口申请 须要提前 1000ms 申请回来,也就是 接口申请 的超时工夫为 2500ms - 1000ms = 1500ms

/**
 * 模仿延时
 * @param {number} delay 延迟时间
 * @returns {Promise<any>}
 */
function sleep(delay) {return new Promise((_, reject) => {setTimeout(() => reject('超时喽'), delay)
  })
}

/**
 * 模仿申请
 */
function request() {
  return new Promise(resolve => {setTimeout(() => resolve('胜利喽'), 1000)
  })
}

/**
 * 判断是否超时
 * @param {() => Promise<any>} requestFn 申请函数
 * @param {number} delay 提早时长
 * @returns {Promise<any>}
 */
function timeoutPromise(requestFn, delay) {return Promise.race([requestFn(), sleep(delay)])
}

2、转盘转完前,接口就申请结束

咱们确保了 接口申请 能够在 转盘转完 之前申请回来,然而还有一个问题,就是须要保障 申请回调 转盘转完回调 同时执行,因为尽管 接口申请 申请回来的时候,转盘还在转着,咱们须要等转盘转完时,再一起执行这两个回调

听到这个形容,置信很多同学就会想到 Promise.all 这个办法

// ... 下面代码

/**
 * 模仿转盘旋转到进行的延时
 * @param {number} delay 延迟时间
 * @returns {Promise<any>}
 */
 function turntableSleep(delay) {
  return new Promise(resolve => {setTimeout(() => resolve('进行转动喽'), delay)
  })
}

/**
 * 判断是否超时
 * @param {() => Promise<any>} requestFn 申请函数
 * @param {number} turntableDelay 转盘转多久
 * @param {number} delay 申请超时时长
 * @returns {Promise<any>}
 */

function zhuanpanPromise(requsetFn, turntableDelay, delay) {return Promise.all([timeoutPromise(requsetFn, delay), turntableSleep(turntableDelay)])
}

3、测试

// 不超时,且先于转盘进行前申请回数据
zhuanpanPromise(request, 2500, 1500).then(res => console.log(res), err => console.log(err))

管制并发的 Promise 的调度器

设想一下,有一天你忽然一次性发了 10 个申请,然而这样的话并发量是很大的,能不能管制一下,就是一次只发 2 个申请,某一个申请完了,就让第 3 个补上,又申请完了,让第 4 个补上,以此类推,让最高并发量变成可控的

addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输入程序是:2 3 1 4

整个的残缺执行流程:一开始 1、2 两个工作开始执行
500ms 时,2 工作执行结束,输入 2,工作 3 开始执行
800ms 时,3 工作执行结束,输入 3,工作 4 开始执行
1000ms 时,1 工作执行结束,输入 1,此时只剩下 4 工作在执行
1200ms 时,4 工作执行结束,输入 4

实现

class Scheduler {constructor(limit) {this.queue = []
    this.limit = limit
    this.count = 0
  }
  

  add(time, order) {const promiseCreator = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(order)
          resolve()}, time)
      })
    }
    this.queue.push(promiseCreator)
  }

  taskStart() {for(let i = 0; i < this.limit; i++) {this.request()
    }
  }

  request() {if (!this.queue.length || this.count >= this.limit) return
    this.count++
    this.queue.shift()().then(() => {
      this.count--
      this.request()})
  }
}

测试

// 测试
const scheduler = new Scheduler(2);
const addTask = (time, order) => {scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

勾销反复申请

举个例子,咱们在做表单提交时,为了避免多次重复的提交,必定会给按钮的点击事件加上 防抖措施 ,这的确是无效地防止了屡次点击造成的反复申请,然而其实还是有弊病的

家喻户晓,为了用户更好地体验, 防抖 的延时是不能太长的,个别在我的我的项目中都是 300ms ,然而这只能管到 申请工夫 < 300ms 的接口申请,如果有一个接口申请须要 2000ms ,那么此时 防抖 也做不到齐全限度 反复申请 ,所以咱们须要额定做一下 勾销反复申请 的解决

实现

实现思路:简略说就是,利用 Promise.race 办法,给每一次申请的身边装置一颗雷,如果第一次申请后,又接了第二次反复申请,那么就执行第一次申请身边的雷,把第一次申请给炸掉,以此类推。

class CancelablePromise {constructor() {
    this.pendingPromise = null
    this.reject = null
  }

  request(requestFn) {if (this.pendingPromise) {this.cancel('勾销反复申请')
    }

    const promise = new Promise((_, reject) => (this.reject = reject))
    this.pendingPromise = Promise.race([requestFn(), promise])
    return this.pendingPromise
  }

  cancel(reason) {this.reject(reason)
    this.pendingPromise = null
  }
}

function request(delay) {return () => 
    new Promise(resolve => {setTimeout(() => {resolve('最初赢家是我')
      }, delay)
    })
}

测试

const cancelPromise = new CancelablePromise()

// 模仿频繁申请 5 次
for (let i = 0; i < 5; i++) {
  cancelPromise
    .request(request(2000))
    .then((res) => console.log(res)) // 最初一个 最初赢家是我
    .catch((err) => console.error(err)); // 前四个 勾销反复申请
}

全局申请 loading

比方一个页面中,或者多个组件中都须要申请并且展现 loading 状态 ,此时咱们不想要每个页面或者组件都写一遍 loading ,那咱们能够对立治理 loading loading 有两种状况

  • 1、全局只有有一个接口还在申请中,就展现 loading
  • 2、全局所有接口都不在申请中,就暗藏 loading

那咱们怎么能力晓得全局接口的申请状态呢?其实咱们能够利用 Promise ,只有某个 接口申请 Promise 的状态不是 pending 那就阐明他申请实现了,无论申请胜利或者失败,既然是无论成功失败,那咱们就会想到 Promise.prototype.finally 这个办法

实现

class PromiseManager {constructor() {this.pendingPromise = new Set()
    this.loading = false
  }

  generateKey() {return `${new Date().getTime()}-${parseInt(Math.random() * 1000)}`
  }

  push(...requestFns) {for (const requestFn of requestFns) {const key = this.generateKey()
      this.pendingPromise.add(key)
      requestFn().finally(() => {this.pendingPromise.delete(key)
        this.loading = this.pendingPromise.size !== 0
      })
    }
  }
}

测试

// 模仿申请
function request(delay) {return () => {
    return new Promise(resolve => {setTimeout(() => resolve('胜利喽'), delay)
    })
  }
}

const manager = new PromiseManager()

manager.push(request(1000), request(2000), request(800), request(2000), request(1500))

const timer = setInterval(() => {
   // 轮询查看 loading 状态
   console.log(manager.loading)
}, 300)

参考

  • Promise 技术点 - 面试实战版

结语

如果你感觉此文对你有一丁点帮忙,点个赞,激励一下林三心哈哈。或者退出我的群哈哈,咱们一起摸鱼一起学习 : meron857287645

正文完
 0