请稍等ManixChen正在解析过程中………



Promise



layout: post
title:  Promise
tags: javascript Promise 
categories:Ajax Promise 

1.Promise 的基本概念 ES6 将其写进了语言标准里统一了用法,是一个构造函数,用来生成 Promise 实例参数为一个执行器函数 (执行器函数是立即执行的), 该函数有两个函数作为参数,第一个参数是成功时的回调,第二个参数是失败时的回调函数的方法有 resolve (可以处理成功和失败)、reject (只处理失败)、all 等方法then、catch、finally 方法为 Promise 实例上的方法

  1. 模拟一个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里的错误信息’); })



3. 手写实现简单的 Promise通过上面的回顾,我们已经了解了 Promise 的关键属性和特点,下面我们一起来实现一个简单的 Promise 吧  // 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里的错误');
  })



3.// 在此函数内, 遇到await会暂停代码往下, 但不影响外面继续执行同步流程
// 等所有主线程同步代码走完, 再执行await并继续向下
console.log(1);
async function myFn() {
    console.log(2);
    const res = await 3
    console.log(res);
    console.log(4);
}
console.log(5);
myFn()
console.log(6);


4. await不能捕获失败结果, 需要使用try+catch关键字捕获
/*
    try和catch语法
    try {
        // 这里放可能在执行中报错的代码
        // 如果报错会终止代码继续执行, 直接跳转进catch里执行
    } catch (err) {
        // err接收try大括号内报错抛出的异常代码
    }
*/
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        // resolve(1)
        reject(new Error('失败'))
    }, 2000)
})
 
async function myFn() {
    try {
        const res = await p
        console.log(res);
    } catch (err) {
        console.error(err)
    }
}
myFn()




5.

1.v1.0 初始版本myPromise function myPromise(constructor){ let self=this; self.status=”pending” //定义状态改变前的初始状态 self.value=undefined;//定义状态为resolved的时候的状态 self.reason=undefined;//定义状态为rejected的时候的状态 function resolve(value){ //两个===”pending”,保证了状态的改变是不可逆的 if(self.status===”pending”){ self.value=value; self.status=”resolved”; } } function reject(reason){ //两个===”pending”,保证了状态的改变是不可逆的 if(self.status===”pending”){ self.reason=reason; self.status=”rejected”; } } //捕获构造异常 try{ constructor(resolve,reject); }catch(e){ reject(e); } } 同时,需要在myPromise的原型上定义链式调用的then方法:

myPromise.prototype.then=function(onFullfilled,onRejected){ let self=this; switch(self.status){ case “resolved”: onFullfilled(self.value); break; case “rejected”: onRejected(self.reason); break; default:
} } 上述就是一个初始版本的myPromise,在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。

var p=new myPromise(function(resolve,reject){resolve(1)}); p.then(function(x){console.log(x)}) //输出1 但是这里myPromise无法处理异步的resolve.比如:

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});

p.then(function(x){console.log(x)}) //无输出 ```

6.v2.0基于观察模式实现 为了处理异步resolve,我们修改myPromise的定义,用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。

function myPromise(constructor){ let self=this; self.status=”pending” //定义状态改变前的初始状态 self.value=undefined;//定义状态为resolved的时候的状态 self.reason=undefined;//定义状态为rejected的时候的状态 self.onFullfilledArray=[]; self.onRejectedArray=[]; function resolve(value){ if(self.status===”pending”){ self.value=value; self.status=”resolved”; self.onFullfilledArray.forEach(function(f){ f(self.value); //如果状态从pending变为resolved, //那么就遍历执行里面的异步方法 });

   }
}
function reject(reason){
   if(self.status==="pending"){
      self.reason=reason;
      self.status="rejected";
      self.onRejectedArray.forEach(function(f){
          f(self.reason);
         //如果状态从pending变为rejected, 
         //那么就遍历执行里面的异步方法
      })
   }
}
//捕获构造异常
try{
   constructor(resolve,reject);
}catch(e){
   reject(e);
} } 对于then方法,状态为pending时,往数组里面添加方法:

myPromise.prototype.then=function(onFullfilled,onRejected){ let self=this; switch(self.status){ case “pending”: self.onFullfilledArray.push(function(){ onFullfilled(self.value) }); self.onRejectedArray.push(function(){ onRejected(self.reason) }); case “resolved”: onFullfilled(self.value); break; case “rejected”: onRejected(self.reason); break; default:
} } 这样,通过两个数组,在状态发生改变之后再开始执行,这样可以处理异步resolve无法调用的问题。这个版本的myPromise就能处理所有的异步,那么这样做就完整了吗?

没有,我们做Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。

3.v3.0then方法实现链式调用 要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分,我们来改造then方法

myPromise.prototype.then=function(onFullfilled,onRejected){ let self=this; let promise2; switch(self.status){ case “pending”: promise2=new myPromise(function(resolve,reject){ self.onFullfilledArray.push(function(){ try{ let temple=onFullfilled(self.value); resolve(temple) }catch(e){ reject(e) //error catch } }); self.onRejectedArray.push(function(){ try{ let temple=onRejected(self.reason); reject(temple) }catch(e){ reject(e)// error catch } }); }) case “resolved”: promise2=new myPromise(function(resolve,reject){ try{ let temple=onFullfilled(self.value); //将上次一then里面的方法传递进下一个Promise的状态 resolve(temple); }catch(e){ reject(e);//error catch } }) break; case “rejected”: promise2=new myPromise(function(resolve,reject){ try{ let temple=onRejected(self.reason); //将then里面的方法传递到下一个Promise的状态里 resolve(temple);
}catch(e){ reject(e); } }) break; default:
} return promise2; } 这样通过then方法返回一个promise就可以实现链式的调用:

p.then(function(x){console.log(x)}).then(function(){console.log(“链式调用1”)}).then(function(){console.log(“链式调用2”)}) //输出 1 链式调用1 链式调用2 这样我们虽然实现了then函数的链式调用,但是还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。

4.v4.0 then函数中的onFullfilled和onRejected方法的返回值问题 特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。

(1)首先来看promise中对于onFullfilled函数的返回值的要求

I)如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误

II)如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise III)如果返回的是一个嵌套类型的promsie,那么需要递归。

IV)如果返回的是非promsie的对象或者函数,那么会选择直接将该对象或者函数,给新的promise。

根据上述返回值的要求,我们要重新的定义resolve函数,这里Promise/A+规范里面称为:resolvePromise函数,该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。

下面我们来看resolvePromise函数的定义:

function resolvePromise(promise,x,resolve,reject){ if(promise===x){ throw new TypeError(“type error”) } let isUsed; if(x!==null&&(typeof x===”object”||typeof x===”function”)){ try{ let then=x.then; if(typeof then===”function”){ //是一个promise的情况 then.call(x,function(y){ if(isUsed)return; isUsed=true; resolvePromise(promise,y,resolve,reject); },function(e){ if(isUsed)return; isUsed=true; reject(e); }) }else{ //仅仅是一个函数或者是对象 resolve(x) } }catch(e){ if(isUsed)return; isUsed=true; reject(e); } }else{ //返回的基本类型,直接resolve resolve(x) } } 改变了resolvePromise函数之后,我们在then方法里面的调用也变成了resolvePromise而不是promise。

myPromise.prototype.then=function(onFullfilled,onRejected){ let self=this; let promise2; switch(self.status){ case “pending”: promise2=new myPromise(function(resolve,reject){ self.onFullfilledArray.push(function(){ setTimeout(function(){ try{ let temple=onFullfilled(self.value); resolvePromise(temple) }catch(e){ reject(e) //error catch } }) }); self.onRejectedArray.push(function(){ setTimeout(function(){ try{ let temple=onRejected(self.reason); resolvePromise(temple) }catch(e){ reject(e)// error catch } }) }); }) case “resolved”: promise2=new myPromise(function(resolve,reject){ setTimeout(function(){ try{ let temple=onFullfilled(self.value); //将上次一then里面的方法传递进下一个Promise状态 resolvePromise(temple); }catch(e){ reject(e);//error catch } }) }) break; case “rejected”: promise2=new myPromise(function(resolve,reject){ setTimeout(function(){ try{ let temple=onRejected(self.reason); //将then里面的方法传递到下一个Promise的状态里 resolvePromise(temple);
}catch(e){ reject(e); } }) }) break; default:
} return promise2; } 这样就能处理onFullfilled各种返回值的情况。

var p=new Promise(function(resolve,reject){resolve(“初始化promise”)}) p.then(function(){return new Promise(function(resolve,reject){resolve(“then里面的promise返回值”)})}).then(function(x){console.log(x)})

//输出 then里面promise的返回值 到这里可能有点乱,我们再理一理,首先返回值有两个:

then函数的返回值——>返回一个新promise,从而实现链式调用

then函数中的onFullfilled和onRejected方法——>返回基本值或者新的promise

这两者其实是有关联的,onFullfilled方法的返回值可以决定then函数的返回值。

五、最后补充Typescript实现的Promise/A+规范(可以忽略此节) interface IConstructor{ (resolve:IResolve,reject:IReject):void } interface IResolve { (x:any):void } interface IReject { (x:any):void } function myPromise(constructor:IConstructor):void{ let self:object=this; self.status=”pending”; self.value=undefined;//if pending->resolved self.reason=undefined;//if pending->rejected self.onFullfilledArray=[];//to deal with async(resolved) self.onRejectedArray=[];//to deal with async(rejeced) let resolve:IResolve; resolve=function(value:any):void{ //pending->resolved if(self.status===”pending”){ self.status=”resolved”; self.value=value; self.onFullfilledArray.forEach(function(f){ f(self.value); }) } } let reject:IReject; reject=function(reason:any):void{ if(self.status===”pending”){ self.status=”rejected”; self.reason=reason; self.onRejectedArray.forEach(function(f){ f(self.reason); }) } } //According to the definition that the function “constructor” accept two parameters //error catch try { constructor(resolve,reject); } catch (e) { reject(e); } }

Jul 19, 2023