javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS Promise

JavaScript实现Promise流程详解

作者:搞前端的小菜

首先呢,Promise是异步中比较重要的知识点,学习的最好方法就是掌握它的基本原理。所以这一篇主要说一下如何用JS来实现一个自己的promise

构造函数

首先我们来看一下我们是如何使用promise的,我们在实例化对象是这么使用的:

    let p1 = new Promise((resolve, reject) => {
      let random = Math.floor(Math.random() * 10);
      if (random > 4) {
        resolve('sucess')
      } else {
        reject('erro')
      }
    })

所以我们在创建我们自己的类要考虑到如何使用这个参数。

我们来看一下, new Promise 的时候传了一个回调函数,在这个回调函数中的代码应该是被立即执行的。

而在这个回调函数中,还带有这两个参数resolve和reject(也是回调函数)。

所以在我们的构造函数中,应该是有这两个函数resolve和reject(暂时先不管这两个函数是做什么的)。

我们知道promise是有三个属性的:

pending : 待定

fulfilled : 对应resolve函数

rejected : 对应reject函数

并且状态一旦改变就不能再更改了。

所以我们的构造函数之中应该有表示当前promise状态的属性。

我们知道不管使用resolve还是reject都会传入一个res变量,作为结果值,所以我们在用一个属性来保存resolve和reject的结果值。

最后我们可以设计出这样的构造函数:

function Mypromise (config) {
  this.status = 'pending';
  this.res = ''
  let resolve = (data) => {
    this.status = 'fulfilled';
    this.res = data
  }
  let reject = (data) => {
    this.status = 'rejected';
    this.res = data
  }
  config(resolve, reject)
}

then 和 catch方法

我们先来回顾一哈怎么使用这两个方法:

    p1
      .then(res => {
        console.log(res);
      })
      .then(res => {
        console.log(res);
      })
      .catch(err => {
        console.log(err);
      })

上面的代码我们可以看到,then和catch方法,都接受了一个回调函数

而这个回调函数的参数也就是我们之前定义的this.res。

所以我们可以想到这么做:

Mypromise.prototype.then = function (config) {
  if (this.status == 'fulfilled') {
    config(this.res)
  }
}
Mypromise.prototype.catch = function (config) {
  if (this.status == 'rejected') {
    config(this.res)
  }
}

但是这种方法不能实现链式调用,就是不能连着使用then方法。

但是如果我想实现出这个模式,我们应该在then方法下回一个对象,而这个对象正常来讲就是this。

所以我们可以直接返回this吗,看下面这个情况。

p1
  .then(res => {
    console.log(res);
    return new Promise((resolve, reject) => {
      resolve('1111')
    })
  })
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  })

在then方法下如果返回了一个新的promise的话,我们就不能直接在then方法里面直接返回this了。

所以我们应该先判断then的回调函数是否返回了新的对象,如果没有才返回当前then的this对象。

Mypromise.prototype.then = function (config) {
  if (this.status == 'fulfilled') {
    var res = config(this.res)
  }
  return res || this;
}
Mypromise.prototype.catch = function (config) {
  if (this.status == 'rejected') {
    var res = config(this.res)
  }
  return res || this;
}

解决异步问题

上面的代码,似乎看着没有什么问题了,但是如果我这么写的话:

    let p2 = new Mypromise((resolve, reject) => {
      setTimeout(() => {
        reject('p2 resolve')
      }, 1000);
    })

问题就大大的出来了,为什么呢? 因为我在p2.then的时候,定时器没有跑完,所以p2的状态现在还是pending,根本不会走下去。

这里面我们用一种经典的解决模式,在我写之前的axios和路由也经常可以看到。

在then方法中,如果当前状态为pending(这句话很重要o),我们就把当前的回调函数保存下来(不一定是一个,有可能是多个then,所以我们采用数组保存)。

那我们保存起来什么时候用呢?当然是在定时器结束后用!那定时器什么时候结束呢?当然是当前promise状态改变的时候,所以,我们在resolve和reject方法之中,要将这些方法进行调用!!!

所以我们要修改构造函数:

function Mypromise (config) {
  this.status = 'pending';
  this.res = '';
  this.saveResolve = [];
  this.saveReject = [];
  let resolve = (data) => {
    if (this.status == 'pending') {
      this.status = 'fulfilled';
      this.res = data
      this.saveResolve.forEach(val => {
        val(this.res)
      })
    }
  }
  let reject = (data) => {
    if (this.status == 'pending') {
      this.status = 'rejected';
      this.res = data
      this.saveReject.forEach(val => {
        val(this.res)
      })
    }
  }
  config(resolve, reject);
}

然后再修改我们的then和catch方法:

Mypromise.prototype.then = function (config) {
  if (this.status == 'pending') {
    this.saveResolve.push(config);
  }
  if (this.status == 'fulfilled') {
    var res = config(this.res)
  }
  return res || this;
}
Mypromise.prototype.catch = function (config) {
  if (this.status == 'pending') {
    this.saveReject.push(config)
  }
  if (this.status == 'rejected') {
    var res = config(this.res)
  }
  return res || this;
}

这样关于异步的问题我们就解决了。

all和race方法

还是老样子,在写之前我们先回顾一下是怎么用的:

    Mypromise.all([p2, p3, p4])
      .then(res => {
        console.log(res);
      })
      .catch(err => {
        console.log(err);
      })
    Mypromise.race([p2, p3, p4])
      .then(res => {
        console.log(res);
      })
      .catch(err => {
        console.log(err);
      })

那我们知道,二者都死以一个数组作为参数,这里面我门就不考虑其他的情况了,我就当数组里面全是promise对象了。。。

二者的区别在于:

all:当所有的promise都执行完,并且状态都为fulfilled,all方法返回的promise为fulfilled,否则为rejected。

race:第一个出现结果的promise对象就是race放回的promise的结果。

现在我们来想一下all方法如何来实现,我们拿到了数组参数之后,一定是要遍历一遍的。

然后对于每一个元素都调用then方法和catch方法。

then方法要有一个结果数组保存每个promise的结果值。

我们可以用一个计数器来计算then方法的调用次数,如果计数器的大小等于数组长度,那么就证明所有的promise全部都是fulfilled,可以返回结果数组。

catch方法只要是被调用了一次,那么直接返回结果,不多bb,直接返回

最后记住要把新的promise返回o。

Mypromise.all = function (arr) {
  let result = [];
  let count = 0;
  let promise = new Mypromise((resolve, reject) => {
    for (var i = 0; i < arr.length; i++) {
      arr[i]
        .then(res => {
          result.push(res);
          count++;
          if (count == arr.length) resolve(result);
        })
        .catch(err => {
          reject(err)
        })
    }
  })
  return promise
}

race的方法的话,实现起来可能就更简单了,不管那个promise的then方法还是catch方法触发了,直接返回结果:

Mypromise.race = function (arr) {
  let promise = new Mypromise((resolve, reject) => {
    for (var i = 0; i < arr.length; i++) {
      arr[i]
        .then(res => {
          resolve(res);
        })
        .catch(err => {
          reject(err)
        })
    }
  })
  return promise
}

到此这篇关于JavaScript实现Promise流程详解的文章就介绍到这了,更多相关JS Promise内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文