Analysis of Promise specifications and principles | JD Logistics Technical Team

Summary

Promise objects are used to clearly handle the completion of asynchronous tasks and return the final result value. This sharing mainly introduces the basic properties of Promise and the basic implementation inside Promise, which can help us clarify usage scenarios and locate problems more quickly.

The reason why Promise appears

First, let’s look at a piece of code: nesting of asynchronous requests

function fn1(params) {
  const xmlHttp = new XMLHttpRequest();
  xmlHttp.onreadystatechange = function(){
    if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
      const fn1Data = {name: 'fn1'}
      console.log(fn1Data, 'fn1Data');
      // 请求2
      (function fn2() {
        xmlHttp.onreadystatechange = function(){
        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
          const fn2Data = {name: `${fn1Data.name}-fn2`}
          console.log(fn2Data, 'fn2Data');
          // 请求3
          (function fn2() {
            xmlHttp.onreadystatechange = function(){
            if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
              const fn3Data = {name: `${fn2Data.name}-fn3`}
              console.log(fn3Data, 'fn3Data');
            }
          }
          xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
          xmlHttp.send();
          })()
        }
      }
      xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
      xmlHttp.send();
      })()
    }
  }
  xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
  xmlHttp.send();
}

fn1()

Or we can optimize the above code into the following

function fn1(params) {
  console.log(`我是fn1,我在函数${params}中执行!!!`);
}
  
function fn2(params) {
  try {
    const xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function(){
      if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        console.log(`我是fn2,我在函数${params}中执行!!!结果是:`,params.data);
        fn1('fn2')
      }
    }
    xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
    xmlHttp.send();
  } catch (error) {
    console.error(error);
  }
}
  
function fn3() {
  try {
    const xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function(){
      if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
          console.log('fn3请求已完成');
          fn2('fn3')
      }
    }
    xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
    xmlHttp.send();
    console.log('我是f3函数呀');
  } catch (error) {
    console.error(error);
  }
}
  
fn3()

It can be seen from the requests written in the above two ways that before promise, in order to make multiple asynchronous requests and rely on the results of the previous asynchronous request, we must nest them layer by layer. In most cases, we also perform data on the asynchronous results. Processing, this makes our code very ugly and difficult to maintain, which forms callback hell, and Promise begins to appear.

Callback Hell Disadvantages

  • bloated code
  • Poor readability
  • High coupling
  • Not easy to handle exceptions

Basic concepts of Promise

meaning

  1. ES6 has written it into the language standard to unify its usage. It is a constructor used to generate Promise instances.
  2. The parameter is an executor function (the executor function is executed immediately). This function has two functions as parameters. The first parameter is a callback on success, and the second parameter is a callback on failure.
  3. Function methods include resolve (can handle success and failure), reject (only handles failure), all and other methods
  4. The then, catch, and finally methods are methods on the Promise instance.

state

  1. pending --- waiting state
  2. Fulfilled --- execution status (resolve callback function, then)
  3. Rejected --- Rejection status (reject callback function, catch)
  4. Once the status changes, it will not change again. The status can only change in two ways, from pending->Fulfilled, pending->Rejected
  5. There are two key attributes: PromiseState --- state changes, PromiseResult --- result data changes
const p1 = Promise.resolve(64)
const p2 = Promise.reject('我错了')
const p3 = Promise.then()
const p4 = Promise.catch()

// 状态改变PromiseState 结果改变PromiseResult
console.log(new Promise(()=>{}), 'Promise');  // PromiseState='pending' PromiseResult=undefined
console.log(p1,'p1');  // PromiseState='Fulfilled' PromiseResult=64
console.log(p2,'p2');  // PromiseState="Rejected" PromiseResult='我错了'
console.log(p3, 'p3'); // then为实例上的方法,报错
console.log(p4, 'p4');  // catch为实例上的方法,报错



Features

1. Clear positioning of error information: Exception information can be captured in the outer layer (network errors and syntax errors can be captured). It has a "bubble" property and will be passed backward until it is captured, so just write a catch at the end.
2. Chain calls: Each then and catch will return a new Promise and pass the result to the next then/catch, so chain calls can be made --- the code is concise and clear

What determines the outcome?

resolve

  1. If the passed parameter is an object of non-Promise type, the returned result is a Promise object in the successful state and enters the next then
  2. If the passed parameter is an object of Promise type, the returned result is determined by the returned Promise. If resolve is returned, it will be a successful state, and it will enter the next then. If it is reject, it will be a failure state, and it will enter the next step. in a catch

reject

  1. If the passed parameter is a non-Promise type object, the returned result is a Promise object in the rejection state, which enters the next catch or the second parameter reject callback of the next then.
  2. If the passed parameter is an object of Promise type, the returned result is determined by the returned Promise. If resolve is returned, it is a successful state and enters the next then. If it is rejected, it is a rejected state and enters the next step. In a catch or in the reject callback, the second parameter of the next then

This is also reflected in our own encapsulated API: why when the code is 1, it is received by then, and everything else is received by catch. It is because the code is judged in then, that is, in the resolve function. If it is 1, Promise is returned. .resolve(), enter then for processing, if it is non-1, return Promise.reject(), enter catch for processing.

flow chart



Simple to use

// 模拟一个promise的get请求
let count = 0
function customGet(url){
    count += 1
    return new Promise((resolve, reject)=>{
        const xmlHttp = new XMLHttpRequest();
        xmlHttp.open("GET",url, true);
        xmlHttp.onload = ()=>{
          console.log(xmlHttp, 'xmlHttp---onload');
          if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
            console.log('customGet请求成功了');
            // 返回非Promise,结果为成功状态
            resolve({data:`第${count}次请求获取数据成功`})

            // 返回Promise,结果由Promise决定
            // resolve(Promise.reject('resolve中返回reject'))
          } else {
            reject('customGet请求错误了')
          }
        }

        // Promise状态改变就不会再变
        // onreadystatechange方法会被执行四次
        // 当地次进来的时候,readyState不等于4,执行else逻辑,执行reject,状态变为Rejected,所以即使再执行if,状态之后不会再改变
        // xmlHttp.onreadystatechange = function(){
        //   console.log(xmlHttp,'xmlHttp---onreadystatechange')
        //   if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        //     console.log('customGet请求成功了');
        //     resolve({data:`第${count}次请求获取数据成功`})
        //   } else {
        //     reject('customGet请求错误了')
        //   }
        // }
        xmlHttp.send();
      })
 }

// 使用Promise,并且进行链式调用
customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{
   console.log(res.data);
   return '第一次请求处理后的数据'
}).then((data)=>{
   console.log(data)
   // console.log(data.toFixed());
   return customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=')
}).then((res)=>{
   console.log(res.data);
}).catch((err)=>{
    // 以类似'冒泡'的性质再外层捕获所有的错误
   console.error(err, '这是catch里的错误信息');
})

Implement simple Promise by handwriting

Through the above review, we have understood the key attributes and characteristics of Promise. Let's implement a simple Promise together.

  // 1、封装一个Promise构造函数,有一个函数参数
  function Promise(executor){
    // 7、添加对象属性PromiseState PromiseResult
    this.PromiseState = 'pending'
    this.PromiseResult = null

    // 14、创建一个保存成功失败回调函数的属性
    this.callback = null

    // 8、this指向问题
    const that = this

    // 4、executor有两个函数参数(resolve,reject)
    function resolve(data){
      // 10、Promise状态只能修改一次(同时记得处理reject中的状态)
      if(that.PromiseState !== 'pending') return

      // console.log(this, 'this');
      // 5、修改对象的状态PromiseState
      that.PromiseState = 'Fulfilled'

      // 6、修改对象的结果PromiseResult
      that.PromiseResult = data

      // 15、异步执行then里的回调函数
      if(that.callback?.onResolve){
        that.callback.onResolve(that.PromiseResult)
      }
    }
    function reject(data){
      console.log(that.PromiseState, 'that.PromiseState');
      if(that.PromiseState !== 'pending') return

      // 9、处理失败函数状态
      that.PromiseState = 'Rejected'
      that.PromiseResult = data
      console.log(that.PromiseResult, 'that.PromiseResult');
      console.log(that.PromiseState, 'that.PromiseState');

      // 16、异步执行then里的回调函数
      if(that.callback?.onReject){
        that.callback.onReject(that.PromiseResult)
      }
    }
    // 3、执行器函数是同步调用的,并且有两个函数参数
    executor(resolve,reject)
  }
  // 2、函数的实例上有方法then
  Promise.prototype.then = function(onResolve,onReject){
    // 20、处理onReject没有的情况
    if(typeof onReject !== 'function'){
      onReject = reason => {
        throw reason
      }
    }
    // 21、处理onResolve没有的情况
    if(typeof onResolve !== 'function'){
      onResolve = value => value
    }
    // 17、每一个then方法都返回一个新的Promise,并且把上一个then返回的结果传递出去
    return new Promise((nextResolve,nextReject)=>{
      // 11、处理成功或失败
      if(this.PromiseState === 'Fulfilled'){
        // 12、将结果传递给函数
        // onResolve(this.PromiseResult)

        // 18、拿到上一次执行完后返回的结果,判断是不是Promise
        const result = onResolve(this.PromiseResult)
        if(result instanceof Promise){
          result.then((v)=>{
            nextResolve(v)
          },(r)=>{
            nextReject(r)
          })
        } else {
          nextResolve(result)
        }
      }
      // 当你一步步写下来的时候有没有怀疑过为什么不用else
       if(this.PromiseState === 'Rejected'){
            // 第12步同时处理此逻辑
            // onReject(this.PromiseResult)

            // 22、处理catch异常穿透捕获错误
            try {
              const result = onReject(this.PromiseResult)
              if(result instanceof Promise){
                result.then((v)=>{
                  nextResolve(v)
                }).catch((r)=>{
                  nextReject(r)
                })
              } else {
                nextReject(result)
              }
            } catch (error) {
              nextReject(this.PromiseResult)
            }
         }
  
      // 13、异步任务时处理成功或失败,想办法等异步任务执行完成后才去执行这两个函数
      if(this.PromiseState === 'pending'){
        this.callback = {
          onResolve,
          onReject
        }
        console.log(this.callback, 'this.callback');
      }
    })
  }
  // 19、函数实例上有方法catch
  Promise.prototype.catch = function(onReject) {
    return this.then(null,onReject)
  }

  // 使用自定义封装的Promise
  const customP = new Promise((resolve,reject)=>{
    // 模拟异步执行请求
    // const xmlHttp = new XMLHttpRequest();
    // xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true);
    // xmlHttp.onload = ()=>{
    //   if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
    //     resolve('success')
    //   } else {
    //     reject('error')
    //   }
    // }
    // xmlHttp.send();

    // 同步执行
    resolve('success')
    // reject('error')
  })

  console.log(customP, 'customP');
  customP.then((res)=>{
    console.log(res, 'resolve回调');
    return '第一次回调'
    // return new Promise((resolve,reject)=>{
    //   reject('错错错')
    // })
  },(err)=>{
    console.error(err, 'reject回调');
    return '2121'
  }).then(()=>{
    console.log('then里面输出');
  }).then().catch((err)=>{
    console.error(err, 'catch里的错误');
  })

For the internal execution order when returning a Promise object in resolve



Summarize

The above is our commonly used basic implementation of Promise. During the implementation process, we compared the advantages and disadvantages of Promise and function nesting for processing asynchronous requests. Promise still has shortcomings, but it is indeed much more convenient. At the same time, we have a clearer understanding of how error handling can perform exception penetration. It can also help us use Promise more standardizedly and quickly locate problems.

Author: JD Logistics Sun Qi

Source: JD Cloud Developer Community Ziyuanqishuo Tech Please indicate the source when reprinting

OpenAI opens ChatGPT Voice Vite 5 for free to all users. It is officially released . Operator's magic operation: disconnecting the network in the background, deactivating broadband accounts, forcing users to change optical modems. Microsoft open source Terminal Chat programmers tampered with ETC balances and embezzled more than 2.6 million yuan a year. Used by the father of Redis Pure C language code implements the Telegram Bot framework. If you are an open source project maintainer, how far can you endure this kind of reply? Microsoft Copilot Web AI will be officially launched on December 1, supporting Chinese OpenAI. Former CEO and President Sam Altman & Greg Brockman joined Microsoft. Broadcom announced the successful acquisition of VMware.
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10150936