前端异步 一步一步详解手写Promise(一)

前言,前端Promise ES6一个比较重要的知识点,很多面试动不动就问到promise实现原理,手写promise, 我们就来手写promise一系列的代码

用过promise 的都知道,构造函数传一个函数,函数里面是两个函数,也就是决定下一个promise的状态的两个回调方法 ,我们先来看看原生的是怎么搞的

new Promise((resolve,reject)=>{
  
})

ok,我们也搞一个构造函数,构造函数里面有两个方法,一个是resolve,一个是reject,我们传入一个函数,构造函数里面直接执行这个函数,然后把构造函数里面的方法作为函数参数传递给我们,我们就可以直接调用构造函数里面的方法了

function MyPromise(callback){

  callback(resolve,reject);
  function resolve(data){
    console.log(data);
  }
  function reject(){
  }
}

new MyPromise((resolve,reject)=>{
  resolve('data1');
})

好,继续往下,promise 不是有个状态嘛,初始化时是pending,所以我们就定义一个状态变量,有一个成功值value,还有一个失败的reason 初始化 ,还有一个then方法,这个方法我们有两种方式可以定义,一种是放在实例上,还有一种是放在原型上,放在实例上的话,就有点多此一举了 ,需要定义一个then属性,还需要定义一个success,failed的两个回调方法,所以我们考虑放到原型上

MyPromise.prototype.then = function(success,failed){
  this.success = success;
  this.failed = failed;
}

这样,我们的框架基本上算是搭好了,当调用resolve 方法 会走构造函数里面的resolve方法,在构造函数可以根据对象状态来执行then 方法里面的回调,因为状态是不可逆的,所以resolve里面需要做一下判断

const PENDING = 'pending';
const RESOLVED = 'fulFilled';
const REJECTED = 'rejected';
const FULFILLED = 'fulfilled';
function MyPromise(callback){

  this.status = PENDING;
  this.value = undefined;
  this.reason = undefined;
  callback(resolve,reject);
  
  function resolve(data){
    if(this.status===PENDING){
      this.success = RESOLVED;
      this.value = data;
      this.onSuccess(data);
    }
  }

  function reject(reason){
    if(this.status===PENDING){
      this.success = REJECTED;
      this.reason = reason;
      this.onfailed(reason);
    }
  }

}
MyPromise.prototype.then = function(onSuccess,onfailed){
  this.onSuccess = success;
  this.onfailed = failed;
}
new MyPromise((resolve,reject)=>{
  resolve('success');
}).then((data)=>{},(err)=>{
  console.log(data);
})

 

我们运行下代码,你会发现说明都没有,应为我们在回调方法里面是直接调用,执行的上下文是window 所以在返回函数的时候需要绑定一下

前端异步 一步一步详解手写Promise(一)_第1张图片

我们继续执行

onSuccess没有定义,说明在调用 onSuccess 没有执行then方法,要不然原型上赋值肯定能访问得到onSuccess方法,所以这里需要异步调用resolve方法,让then方法先执行,把成功和失败的回调赋值给原型

new MyPromise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('success');
  },1000)
}).then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err);
})

 

ok ,基本上的功能是已经完成了,但是有个问题,有如下骚操作。。。。 这样我们的回调只能执行一次 后面的then方法把前面的onSuccess 和 onFailed方法覆盖掉了,所以只会回调的时候只会执行最后一个then里面的代码

var p = new MyPromise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('success');
  },1000)
})
p.then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err);
})
p.then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err);
})
p.then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err);
})
p.then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err);
})

这样我们的代码就得改,发布订阅者的逻辑是个好东西啊(iOS RAC 就是这个原理),把要执行的回调放到一个数组里面,需要执行的时候全部遍历去执行

前端异步 一步一步详解手写Promise(一)_第2张图片

MyPromise.prototype.then = function(onSuccess,onfailed){

  /*因为原生的Promise then 里面的成功和失败可以不传,如果不传的话,我们默认给一个匿名方法 返回传入的参数
  避免报错
  */
  var onSuccessFn =  typeof onSuccess==='function'? onSuccess:value=>value
  var onfailedFn =  typeof onfailed==='function'? onfailed:reason=>{throw reason}
  let self = this;
  if(this.status===FULFILLED){
    onSuccessFn(this.value);
  }
  if(this.status===REJECTED){
    onfailedFn(this.reason);
  }
  if(this.status===PENDING){
    self.onFulfilled.push(onSuccessFn);
    self.onRejected.push(onfailedFn);
  }
  
}

到此,我们的第一阶段可以告一段落了,我们进入第二阶段,Promise是可以一直then 的,所以我们的then 方法一定要返回一个promise对象,promise 的下一个then走成功还是走失败取决于上一个promise then 走成功还是失败,所以我们要拿到上一个then 方法的返回值,then方法继续改造,这里可能有点绕,仔细读一下

我直接贴代码了

MyPromise.prototype.then = function(onSuccess,onfailed){

  /*因为原生的Promise then 里面的成功和失败可以不传,如果不传的话,我们默认给一个匿名方法 返回传入的参数
  避免报错
  */
  var onSuccessFn =  typeof onSuccess==='function'? onSuccess:value=>value;
  var onfailedFn =  typeof onfailed==='function'? onfailed:reason=>{throw reason};
  let self = this;//这里的self 就是上个prosie对象
  /* 
  下一个Promise then的回调的执行状态是根据上一个then的返回值来处理的 
  所以封装一个方法,统一处理
  */
  function resolvePromise(promise2,x,resolve,reject){
    if(x===promise2){//如果下一个promise和上一个是同一个对象,那就会无限循环
      reject(new TypeError('Chaining cycle'));
    }
    if(x &&typeof x==='function'|| typeof x==='object'){
      /*
      1.为什么要尝试获取then方法,因为不获取可能会出问题,比如如下代码直接调用就报错了
        const object1 = {};
        Object.defineProperty(object1, 'then', {
          get:function(){
            throw Error('error')
          }
        });
      2.获取到then方法之后,可以尝试去执行下它,这里的then 可能是系统的Promise的then 
      或者我们自定义的MyPromise的then,还有一种是我们自定义的对象自己搞个then方法那就走完它的then方法就结束了
      */
      try {
        let then = x.then;
        if(typeof then==='function'){
          then.call(x,(y)=>{
            resolve(y)
          },(n)=>{
            reject(n)
          })
        }else{
          resolve(x)
        }
      } catch (error) {
        reject(error)
      }
    }else(
      resolve(x)
    )

  }

  let promise2 = new MyPromise((resolve,reject)=>{

    if(self.status===FULFILLED){
      
      /*
      这里可能不太好理解,x = onSuccessFn() 或者 x = onfailedFn()
      就是上个then的返回值 执行onSuccessFn()/onfailedFn() 就会执行当前promise的then里面的
      成功或者失败方法,返回值就是我们想要的,我们根据这个返回值的类型对应做一些判断操作
      这两句代码需要异步执行,同步调用的话,then执行,promise2还没有返回,这样promise2
      就是未定义了 下面逻辑是同理的
      */
      setTimeout(()=>{
        let x =  onSuccessFn(self.value);//这里的x就是上一个then的返回值
        resolvePromise(promise2,x,resolve,reject);
      },0)

    }
    if(self.status===REJECTED){
      setTimeout(()=>{
        let x  = onfailedFn(self.reason);
        resolvePromise(promise2,x,resolve,reject);
      },0)
    }

    if(self.status===PENDING){

      self.onFulfilled.push(()=>{
        setTimeout(()=>{
          let x  = onSuccessFn(self.value);
          resolvePromise(promise2,x,resolve,reject)
        },0)
      });

      self.onRejected.push(()=>{
       setTimeout(()=>{
        let x  = onfailedFn(self.reason);
        resolvePromise(promise2,x,resolve,reject)
       },0)
      });
    }

  })
  return promise2
}

功能基本上搞定了,我们来测试下代码

前端异步 一步一步详解手写Promise(一)_第3张图片执行结果 前端异步 一步一步详解手写Promise(一)_第4张图片

结果是我们想要的 你以为大功告成了吗?

 

然而并没有,还有一种情况,resolve里面的对象又是一个promise对象,写个代码来看看结果

前端异步 一步一步详解手写Promise(一)_第5张图片 执行结果 前端异步 一步一步详解手写Promise(一)_第6张图片

还是一个是一个promise对象,这就不科学了啊,应该拿到最里面的值,所以就需要递归调用一下

前端异步 一步一步详解手写Promise(一)_第7张图片

好的,至此,你以为基本上大功告成了吗?

然而。

其实差不多啦 ,还有个细节需要优化就是在递归调用的时候,promise的状态只能改一次,我们添加一个变量 如果改变过我们就直接返回

我觉得此操作稍稍有点多余,因为我们在构成函数里面写的reject resolve 方法一旦状态不是pedding了就不能改了,即使你在调用也不会在走里面的方法

不过我们加个判断,减少代码的执行也是蛮好的。。。 至此手写Promise 完,下面我们来手写 promise.All promise.race, promise.allSettled

前端异步 一步一步详解手写Promise(一)_第8张图片

贴一下全部代码

const PENDING = 'pending';
const REJECTED = 'rejected';
const FULFILLED = 'fulfilled';

function MyPromise(callback){

  this.status = PENDING;
  this.value = undefined;
  this.reason = undefined;

  this.onFulfilled = [];//成功的回调
  this.onRejected = []; //失败的回调

  callback(resolve.bind(this),reject.bind(this));

  function resolve(data){
    if(this.status===PENDING){
      this.status = FULFILLED;
      this.value = data;
      this.onFulfilled.forEach(fn => {
        fn(data);
      });
    }
  }


  function reject(reason){
    if(this.status===PENDING){
      this.status = REJECTED;
      this.reason = reason;
      this.onFulfilled.forEach(fn => {
        fn(reason);
      });
    }
  }
}

MyPromise.prototype.then = function(onSuccess,onfailed){

  /*因为原生的Promise then 里面的成功和失败可以不传,如果不传的话,我们默认给一个匿名方法 返回传入的参数
  避免报错
  */
  var onSuccessFn =  typeof onSuccess==='function'? onSuccess:value=>value;
  var onfailedFn =  typeof onfailed==='function'? onfailed:reason=>{throw reason};
  let self = this;//这里的self 就是上个prosie对象
  /* 
  下一个Promise then的回调的执行状态是根据上一个then的返回值来处理的 
  所以封装一个方法,统一处理
  */
  function resolvePromise(promise2,x,resolve,reject){
    if(x===promise2){//如果下一个promise和上一个是同一个对象,那就会无限循环
      reject(new TypeError('Chaining cycle'));
    }
    if(x && typeof x==='function'|| typeof x==='object'){
      /*
      1.为什么要尝试获取then方法,因为不获取可能会出问题,比如如下代码直接调用就报错了
        const object1 = {};
        Object.defineProperty(object1, 'then', {
          get:function(){
            throw Error('error')
          }
        });
      2.获取到then方法之后,可以尝试去执行下它,这里的then 可能是系统的Promise的then 
      或者我们自定义的MyPromise的then,还有一种是我们自定义的对象自己搞个then方法
      */
      let changed;
      try {
        let then = x.then;
        if(typeof then==='function'){
          then.call(x,(y)=>{
            if(changed) return;
            changed = true;
            // 这里递归调用一下,知道y不是一个Promise对象(包含then方法),也就是其它对象 或者其它值
            resolvePromise(promise2,y,resolve,reject)
          },(n)=>{
            if(changed) return;
            changed = true;
            reject(n)
          })
        }else{
          if(changed) return;
          changed = true;
          resolve(x)
        }
      } catch (error) {
        if(changed) return;
        changed = true;
        reject(error)
      }
    }else(
      resolve(x)
    )
  }

  let promise2 = new MyPromise((resolve,reject)=>{

    if(self.status===FULFILLED){
      
      /*
      这里可能不太好理解,x = onSuccessFn() 或者 x = onfailedFn()
      就是上个then的返回值 执行onSuccessFn()/onfailedFn() 就会执行当前promise的then里面的
      成功或者失败方法,返回值就是我们想要的,我们根据这个返回值的类型对应做一些判断操作
      这两句代码需要异步执行,同步调用的话,then执行,promise2还没有返回,这样promise2
      就是未定义了 下面逻辑是同理的
      */
      setTimeout(()=>{
        let x =  onSuccessFn(self.value);//这里的x就是上一个then的返回值
        resolvePromise(promise2,x,resolve,reject);
      },0)

    }
    if(self.status===REJECTED){
      setTimeout(()=>{
        let x  = onfailedFn(self.reason);
        resolvePromise(promise2,x,resolve,reject);
      },0)
    }

    if(self.status===PENDING){

      self.onFulfilled.push(()=>{

        setTimeout(()=>{
          let x  = onSuccessFn(self.value);
          resolvePromise(promise2,x,resolve,reject)
        },0)

      });

      self.onRejected.push(()=>{
       setTimeout(()=>{
        let x  = onfailedFn(self.reason);
        resolvePromise(promise2,x,resolve,reject)
       },0)
      });
    }

  })
  return promise2
}

var p = new MyPromise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('success');
  },0)
}).then((data)=>{
  
  return new MyPromise((resolve,reject)=>{
    resolve(
      new MyPromise((resolve,reject)=>{
      // resolve('success1');
      reject('error1')
      resolve('error1')
      })
    )
  })
}).then((data)=>{
  console.log(data);
  return {a:5}
}).then((data)=>{
  console.log(data);
})

 

你可能感兴趣的:(前端异步,js,javascript)