18
头图

Preface

Hello, everyone, I’m Lin Sanxin, in the most easy-to-understand terms, is my motto, based on the premise of advanced is my original intention, as Promise in our development It is very important. I think there are three levels for the use level of Promise

  • 1. Master the basic usage Promise
  • 2. Master the basic principles Promise
  • 3. In the project, Promise solve some problems

The first point, then, in fact, be able to grasp Promise some of the basic methods of use and methods, such as then、catch、all、race、finally、allSettled、any、resolve etc.

The second point, then, is simply to be able to achieve what Promise principle, which enables us to Promise have a better understanding of those common method

The third point, then, is to be flexible Promise solve some of the problems in our development, and today I'll tell you that what I Promise solve the problem in project development it!

Interface request timed out

As the name implies, given a time, if the interface request exceeds this time, an error will be reported

1. Realize by yourself

The idea is to achieve: interface request and delay function race, and the use of a Promise wrapped, because Promise state is not reversible, so if interface request to the finish described expires and Promise state is fulfilled , and vice versa , delay function is described to finish timeout and Promise state is rejetced , according to the last Promise determine the status of the presence or absence of timeout

截屏2021-11-22 下午9.58.49.png

/**
 * 模拟延时
 * @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

In fact, the code timeoutPromise Promise.race , which has the same effect

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

3. Test

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

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

Turntable lottery

When we usually draw a lottery on the turntable, we usually start to spin and initiate an interface request at the same time, so there are two possibilities

  • 1. After the turntable is turned, the interface has not requested to come back, which is abnormal
  • 2. The interface request is completed before the turntable is finished. This is normal, but it is necessary to ensure that the request callback and the turntable callback are executed at the same time.

1. After the turntable is turned, the interface has not requested to come back

The main problem is how to judge whether the request time of the interface exceeds the turntable to complete the turntable. In fact, we can use the previous knowledge point interface request timeout, all of which are the same. If rotary table is the time required for complete 2500ms , that we can define interface request in advance 1000ms request back, i.e. timeout interface request is 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. The interface request is completed before the turntable is finished

Let's make sure the interface request can request back before the rotary table finish, but there is a problem, is the need to ensure request a callback with rotary table complete the callback is executed at the same time, because although time interface request request back, still turntable Turn, we need to wait for the turntable to finish before executing these two callbacks together

Hearing this description, I believe many students will think of the method 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. Test

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

Scheduler that controls concurrent Promise

Imagine that one day you suddenly send 10 requests at a time, but in this case the amount of concurrency is very large, can you control it, that is, only send 2 requests at a time, and let the third one fill up when one request is over. , The request is over again, let the fourth one fill up, and so on, so that the highest concurrency becomes controllable

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

accomplish

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()
    })
  }
}

test

// 测试
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();

Cancel duplicate request

For example, when we are doing form submission, in order to prevent multiple repeated submissions, we will definitely add anti-shake measures to the click event of the button. This is indeed effective in avoiding repeated requests caused by multiple clicks, but in fact There are still drawbacks

As we all know, for a better user experience, the anti-shake cannot be too long. Generally 300ms in my project, but this can only with the interface request with 161a821a4b4011 request time <300ms. If there is an interface request Need 2000ms , then anti-shake can not completely limit repeated requests, so we need to do an extra cancel the processing of repeated requests

accomplish

Realization idea: Simply put, use the Promise.race method to install a mine next to each request. If the second request is received after the first request, then the mine next to the first request will be executed. The first request was bombed, and so on.

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)
    })
}

test

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)); // 前四个 取消重复请求
}

Global request loading

For example, a page or more of the components are need to request and display loading state, at this time we do not want every page or component are written again loading , then we can be unified management loading , loading there are two cases

  • 1. As long as there is an interface still in the global request, it will show loading
  • 2. If all interfaces in the world are not in the request, then loading

Then how can we know the request status of the global interface? In fact, we can use Promise . As long as the Promise status of interface request pending it means that his request is completed. No matter whether the request succeeds or fails, since it is a success or failure, then we will think of the method Promise.prototype.finally

accomplish

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
      })
    }
  }
}

test

// 模拟请求
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)

refer to

Concluding remarks

If you think this article is of little help to you, please give me a thumbs up and encourage Lin Sanxin haha. Or you can join my fish-fishing school. Let's study together, ah, ah, ah, ah, I will mock interviews regularly, resume guidance, answer questions, and we will learn from each other and make progress together! !
截屏2021-11-28 上午9.43.19.png


Sunshine_Lin
2.1k 声望7.1k 粉丝