资讯专栏INFORMATION COLUMN

一篇文章用ES6手撸一个Promise

hsluoyz / 1314人阅读

摘要:本篇文章将会尝试用简单易懂的语言描述的原理,并且用手撸一个简单的。一个后可以通过方法,指定和时的回调函数。实现实现状态机因为是一个构造函数,使用的写法,首先想到的就是有显式声明的。

说到Promise,都知道它是比回调函数更优的一种异步编程解决方案,它可以使得异步操作逻辑变得更加清晰,是解决地狱回调的一种尝试。本篇文章将会尝试用简单易懂的语言描述Promise的原理,并且用es6手撸一个简单的Promise。
1. Promise的原理
原理:promise对象有三种状态,pending、fulfilled和rejected。promise对象内部保存一个需要执行一段时间的异步操作,当异步操作执行结束后可以调用resolve或reject方法,来改变promise对象的状态,状态一旦改变就不能再变。new一个promise后可以通过then方法,指定resolved和rejected时的回调函数。下面是我们日常使用Promise的代码逻辑。
let getAsyncData = new Promise((resolve, reject) => {
    // 执行一些异步操作
    if (// 如果成功) {
        // ...执行代码
        resolve();
    } else { // 如果失败
        // ...执行代码
        reject();
    }
})

);
getAsyncData.then(success, fail).then(success, fail)

结合Promise A+规范,我们就可以分析一下我们要实现一个什么东西:

实现一个状态机,有三个状态,pending、fulfilled、rejected,状态之间的转化只能是pending->fulfilled、pending->rejected,状态变化不可逆。

实现一个then方法,可以用来设置成功和失败的回调

then方法要能被调用多次,所以then方法需要每次返回一个新的promise对象,这样才能支持链式调用。

构造函数内部要有一个value值,用来保存上次执行的结果值,如果报错,则保存的是异常信息。

那我们现在就按照上面提到的原理和规范来实现这个Promise构造函数。

2. 实现 2.1 实现状态机
// promise.js
class Promise {
  constructor (executor) {
    this.status = PENDING;
    this.value = "";
    executor(this.resolve, this.reject);
  }

  resolve (value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED; 
    }
  }

  reject (value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = REJECTED;
    }
  }

  then (onfulfilled, onrejected) {
    if (this.status === FULFILLED) {
      onfulfilled(this.value);
    }
    if (this.status === REJECTED) {
      onrejected(this.value);
    }
  }
}

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

const test = new Promise((resolve, reject) => {
  resolve(100);
});
test.then((data) => {
  console.log(data);
},(data) => {});

因为Promise是一个构造函数,使用ES6的写法,首先想到的就是有显式constructor声明的class。上面就是我们用class的实现,可以看到这样我们就实现了这个状态机,有status, value两个属性和resolve, reject, then三个函数;同时它有pending, fulfilled和rejected三个状态,其中pending就可以切换为fulfilled或者rejected两种。

看来起还行的样子,尝试着运行一下,报错了。

ReferenceError: resolve is not defined

这是因为在class中使用this要格外小心,类的方法内部如果含有this,它默认指向类的实例,而如果多带带使用这个方法(上面代码中的resolve(100)),this就会指向该方法运行时所在的环境,从而因为找不到这个方法而报错。所以,要在构造函数中绑定this。constructor改为

  constructor (executor) {
    this.status = PENDING;
    this.value = "";
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    this.then = this.then.bind(this);
    executor(this.resolve, this.reject);
  }

再运行一下,输出了100,但是现在其实不是一个异步处理方案,代码先运行了resolve(100)然后又运行了then函数,其实对于异步的情况没有处理,不信的话就给resolve加一个setTimeout,好了,代码又没有输出了。

2.2 实现异步设置状态

作为一个异步处理的函数,在使用的时候,我们肯定是会先设置好不同异步返回后的处理逻辑(即then的成功、失败调用函数),然后安心等待异步执行,最后再异步结束后,系统会自动根据我们的逻辑选择调用不同回调函数。换句话说,then函数要对status为pending的状态进行处理。处理的原理是设置两个数组,在pending状态下分别保存成功和失败回调函数,当状态改变后,再根据状态去调用数组中保存的回调函数

我们将代码改变如下:

class Promise {
  constructor (executor) {
    this.status = PENDING;
    this.value = "";
    this.onfulfilledArr = [];
    this.onrejectedArr = [];
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    this.then = this.then.bind(this);
    executor(this.resolve, this.reject);
  }

  resolve (value) {
    if (this.status === PENDING) {
      this.value = value;
      this.onfulfilledArr.forEach(item => {
        item(this.value);
      })
      this.status = FULFILLED; 
    }
  }

  reject (value) {
    if (this.status === PENDING) {
      this.value = value;
      this.onrejectedArr.forEach(item => {
        item(this.value);
      })
      this.status = REJECTED;
    }
  }

  then (onfulfilled, onrejected) {
    if (this.status === FULFILLED) {
      onfulfilled(this.value);
    }
    if (this.status === REJECTED) {
      onrejected(this.value);
    }
    if (this.status === PENDING) {
      this.onfulfilledArr.push(onfulfilled);
      this.onrejectedArr.push(onrejected);
    }
  }
}

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

const test = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(100);
  }, 2000)
});
test.then((data) => {
  console.log(data);
},(data) => {});

再运行一下,ok正常输出了。但是Promise的一大特点就是可以链式调用,即test.then(success, fail).then(success, fail)...这就需要then返回一个新的Promise对象,而我们的程序现在明显的是不支持的。那么继续改一下。

2.3 实现链式调用

再观察一下链式调用,如果成功和失败的函数中有返回值,这个值要作为参数传给下个then函数的成功或失败回调。所以我们要在返回的new Promise中调用相应的函数。

class Promise {
  constructor (executor) {
    this.status = PENDING;
    this.value = "";
    this.onfulfilledArr = [];
    this.onrejectedArr = [];
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    this.then = this.then.bind(this);
    executor(this.resolve, this.reject);
  }

  resolve (value) {
    if (this.status === PENDING) {
      this.value = value;
      this.onfulfilledArr.forEach(item => {
        item(this.value);
      })
      this.status = FULFILLED; 
    }
  }

  reject (value) {
    if (this.status === PENDING) {
      this.value = value;
      this.onrejectedArr.forEach(item => {
        item(this.value);
      })
      this.status = REJECTED;
    }
  }

  then (onfulfilled, onrejected) {
    if (this.status === FULFILLED) {
      const res = onfulfilled(this.value);
      return new Promise(function(resolve, reject) {
        resolve(res);
      })
    }
    if (this.status === REJECTED) {
      const res = onrejected(this.value);
      return new Promise(function(resolve, reject) {
        reject(res);
      })
    }
    if (this.status === PENDING) {
      const self = this;
      return new Promise(function(resolve, reject) {
        self.onfulfilledArr.push(() => {
          const res = onfulfilled(self.value)
          resolve(res);
        });
        self.onrejectedArr.push(() => {
          const res = onrejected(self.value)
          reject(res);
        });
      })
    }
  }
}

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

const test = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(100);
  }, 2000)
});
test.then((data) => {
  console.log(data);
  return data + 5;
},(data) => {})
.then((data) => {
  console.log(data)
},(data) => {});

再运行一下,输出100,105。好了,一个简单的Promise就实现好了。

3. 总结

Promise其实就是对异步操作的一种封装方式,可以使得回调的流程变得清晰一些,但是本质上并不解决回调地狱。因为如果有多个异步操作嵌套,then也要一直写下去。所以后来ES6又有了Generator,允许我们用同步的方式来写异步的代码,以及它的语法糖async/await,当然这就是后话了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96847.html

相关文章

  • 新人学习,照文档手撸webpack,bug基本修复完善(有问题请联系我,并非完全原创,不喜勿喷。。。

    摘要:介绍简单点来说就就是一个配置文件,所有的魔力都是在这一个文件中发生的。一安装全局安装在文件夹里面也需要安装这个是在你根目录下进行的全局安装记得加太慢,推荐用的镜像安装方法一样。二创建项目新建文件夹命令行输入命令。 介绍:webpack简单点来说就就是一个配置文件,所有的魔力都是在这一个文件中发生的。 这个配置文件主要分为三大块 entry 入口文件 让webpack用哪个文件作为项目的...

    xiangchaobin 评论0 收藏0
  • ES6中的异步编程:Generators函数+Promise:最强大的异步处理方式

    摘要:更好的异步编程上面的方法可以适用于那些比较简单的异步工作流程。小结的组合目前是最强大,也是最优雅的异步流程管理编程方式。 访问原文地址 generators主要作用就是提供了一种,单线程的,很像同步方法的编程风格,方便你把异步实现的那些细节藏在别处。这让我们可以用一种很自然的方式书写我们代码中的流程和状态逻辑,不再需要去遵循那些奇怪的异步编程风格。 换句话说,通过将我们generato...

    Taonce 评论0 收藏0
  • 手写款符合Promise/A+规范的Promise

    摘要:手写一款符合规范的长篇预警有点长,可以选择性观看。初始状态是,状态可以有或者不能从转换为或者从转换成即只要由状态转换为其他状态后,状态就不可变更。 手写一款符合Promise/A+规范的Promise 长篇预警!有点长,可以选择性观看。如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的。主要是代码部分有点多,不过好多都是重复的,不...

    rubyshen 评论0 收藏0
  • 推荐于压缩图片的JS插件:localResizeIMG

    摘要:图片压缩的原理大同小异,这里直接引用官方文档的原话基本原理是通过渲染图片,再通过方法压缩保存为字符串能够编译为格式的图片。这个过程我自己手撸过,代码很多,更不用提有各种的兼容性坑,所以最后权衡再三还是直接换成了这个插件。 惯例,先贴传送门:https://github.com/think2011/localResizeIMG 首先说到,为嘛要压缩图片,这需求一般出现在需要上传照片(尤其...

    Dogee 评论0 收藏0

发表评论

0条评论

hsluoyz

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<