资讯专栏INFORMATION COLUMN

从零开始实现一个自己的Promise库

paulquei / 1903人阅读

摘要:所以,这篇文章我会带大家从零开始,手写一个基本能用的。首先,规定对象是一个构造函数,用来生成实例。然后,这个构造函数接受一个函数作为参数,该函数的两个参数分别是和。对象通过自身的状态,来控制异步操作。

刚开始写前端的时候,处理异步请求经常用callback,简单又顺手。后来写着写着就抛弃了callback,开始用promise来处理异步问题。promise写起来确实更加优美,但由于缺乏对它内部结构的深刻认识,每次在遇到一些复杂的情况时,promise用起来总是不那么得心应手,debug也得搞半天。

所以,这篇文章我会带大家从零开始,手写一个基本能用的promise。跟着我写下来以后,你会对promise是什么以及它的内部结构有一个清楚的认知,未来在复杂场景下使用promise也能如鱼得水。

什么是Promise

回到正文,什么是Promise?说白了,promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

首先,ES6规定Promise对象是一个构造函数,用来生成Promise实例。然后,这个构造函数接受一个函数(executor)作为参数,该函数的两个参数分别是resolve和reject。最后,Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(onFulfilled和onRejected)。

具体的使用方法,用代码表现是这样:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

理解了这个后,我们就可以大胆的开始构造我们自己的promise了,我们给它取个名字:CutePromise

实现一个Promise:CutePromise

我们直接用ES6的class来创建我们的CutePromise,对ES6语法还不熟悉的,可以先读一下我的另外两篇介绍ES6核心语法的文章后再回来。30分钟掌握ES6/ES2015核心内容(上)、30分钟掌握ES6/ES2015核心内容(下)

class CutePromise {

  // executor是我们实例化CutePromise时传入的参数函数,它接受两个参数,分别是resolve和reject。
  // resolve和reject我们将会定义在constructor当中,供executor在执行的时候调用
  constructor(executor) {
    const resolve = () => {}
    const reject = () => {}
    
    executor(resolve, reject)
  }

  // 为实例提供一个then的方法,接收两个参数函数,
  // 第一个参数函数必传,它会在promise已成功(fulfilled)以后被调用
  // 第二个参数非必传,它会在promise已失败(rejected)以后被调用
  then(onFulfilled, onRejected) {}

}

创建了我们的CutePromise后,我们再来搞清楚一个关键点:Promise 对象的状态。

Promise 对象通过自身的状态,来控制异步操作。一个Promise 实例具有三种状态:

异步操作未完成(pending)

异步操作成功(fulfilled)

异步操作失败(rejected)

上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。状态的切换只有两条路径:第一种是从pending=>fulfilled,另一种是从pending=>rejected,状态一旦切换就不能再改变。

现在我们来为CutePromise添加状态,大概流程就是:
首先,实例化初始过程中,我们先将状态设为PENDING,然后当executor执行resolve的时候,将状态更改为FULFILLED,当executor执行reject的时候将状态更改为REJECTED。同时更新实例的value。

constructor(executor) {
    ...
    this.state = "PENDING";
    ...
    const resolve = (result) => {
      this.state = "FULFILLED";
      this.value = result;
    }
    const reject = (error) => {
      this.state = "REJECTED";
      this.value = error;
    }
    ...
}

再来看下我们的then函数。then函数的两个参数,onFulfilled表示当promise异步操作成功时调用的函数,onRejected表示当promise异步操作失败时调用的函数。假如我们调用then的时候,promise已经执行完成了(当任务是个同步任务时),我们可以直接根据实例的状态来执行相应的函数。假如promise的状态还是PENDING, 那我们就将onFulfilled和onRejected直接存储到chained这个变量当中,等promise执行完再调用。

constructor(executor) {
    ...
    this.state = "PENDING";
    
    // chained用来储存promise执行完成以后,需要被依次调用的一系列函数
    this.chained = [];
    
    const resolve = (result) => {
      this.state = "FULFILLED";
      this.value = result;
      
      // promise已经执行成功了,可以依次调用.then()函数里的onFulfilled函数了
      for (const { onFulfilled } of this.chained) {
          onFulfilled(res);
      }
    }

    ...
}
then(onFulfilled, onRejected) {
  if (this.state === "FULFILLED") {
    onFulfilled(this.value);
  } else if (this.state === "REJECTED") {
    onRejected(this.value);
  } else {
    this.$chained.push({ onFulfilled, onRejected });
  }
}

这样我们就完成了一个CutePromise的创建,下面是完整代码,大家可以复制代码到控制台测试一下:

class CutePromise {

  constructor(executor) {
    if (typeof executor !== "function") {
      throw new Error("Executor must be a function");
    }

    this.state = "PENDING";
    this.chained = [];

    const resolve = res => {
      if (this.state !== "PENDING") {
        return;
      }

      this.state = "FULFILLED";
      this.internalValue = res;

      for (const { onFulfilled } of this.chained) {
        onFulfilled(res);
      }
    };
    const reject = err => {
      if (this.state !== "PENDING") {
        return;
      }
      this.state = "REJECTED";
      this.internalValue = err;
      for (const { onRejected } of this.chained) {
        onRejected(err);
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
    
  then(onFulfilled, onRejected) {
    if (this.state === "FULFILLED") {
      onFulfilled(this.internalValue);
    } else if (this.$state === "REJECTED") {
      onRejected(this.internalValue);
    } else {
      this.chained.push({ onFulfilled, onRejected });
    }
  }
}

提供一下测试代码:

let p = new CutePromise(resolve => {
  setTimeout(() => resolve("Hello"), 100);
});

p.then(res => console.log(res));

p = new CutePromise((resolve, reject) => {
  setTimeout(() => reject(new Error("woops")), 100);
});

p.then(() => {}, err => console.log("Async error:", err.stack));

p = new CutePromise(() => { throw new Error("woops"); });

p.then(() => {}, err => console.log("Sync error:", err.stack));
实现链式调用

实现链式调用其实很简单,只需要在我们定义的then()方法里返回一个新的CutePromise即可。

  then(onFulfilled, onRejected) {
    return new CutePromise((resolve, reject) => {

      const _onFulfilled = res => {
        try {
          //注意这里resolve有可能要处理的是一个promise
          resolve(onFulfilled(res));
        } catch (err) {
          reject(err);
        }
      };
      const _onRejected = err => {
        try {
          reject(onRejected(err));
        } catch (_err) {
          reject(_err);
        }
      };
      if (this.state === "FULFILLED") {
        _onFulfilled(this.internalValue);
      } else if (this.state === "REJECTED") {
        _onRejected(this.internalValue);
      } else {
        this.chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected });
      }
    });
  }

不过,我们还需要解决一个问题:假如then函数的第一个参数onfulfilled()本身返回的也是一个promise怎么办?比如下面这种使用方式,其实是最真实项目场景中最常见:

p = new CutePromise(resolve => {
  setTimeout(() => resolve("World"), 100);
});

p.
  then(res => new CutePromise(resolve => resolve(`Hello, ${res}`))).
  then(res => console.log(res));

所以我们需要让我们的resolve方法能够处理promise:

const resolve = res => {
      if (this.state !== "PENDING") {
        return;
      }
      
      // 假如说res这个对象有then的方法,我们就认为res是一个promise
      if (res != null && typeof res.then === "function") {
        return res.then(resolve, reject);
      }
    ...
}
三道思考题

promise array的链式调用?

promise怎么做并发控制?

promise怎么做异步缓存?

以上三道思考题其实跟你用不用promise并没有多大关系,但是如果你不深刻理解promise想要解决这三个问题还真不是那么轻松的。

参考:https://brunoscopelliti.com/l...

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

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

相关文章

  • 前端之从零开始系列

    摘要:只有动手,你才能真的理解作者的构思的巧妙只有动手,你才能真正掌握一门技术持续更新中项目地址求求求源码系列跟一起学如何写函数库中高级前端面试手写代码无敌秘籍如何用不到行代码写一款属于自己的类库原理讲解实现一个对象遵循规范实战手摸手,带你用撸 Do it yourself!!! 只有动手,你才能真的理解作者的构思的巧妙 只有动手,你才能真正掌握一门技术 持续更新中…… 项目地址 https...

    Youngdze 评论0 收藏0
  • 从零开始一个 Promise

    摘要:是什么在规范中,是一个类,它的构造函数接受一个函数。在这种情况下,是但处于状态。与一起使用关键字会暂停执行一个函数,直到等待的变成状态。此外,会一直等待调用直到下一个时序。 原文:Write Your Own Node.js Promise Library from Scratch作者:code_barbarian Promise 已经是 JavaScript 中异步处理的基石,回调...

    Binguner 评论0 收藏0
  • 从零开始搭建React同构应用(二):浏览器端工程化

    摘要:从零开始搭建同构应用二项目工程化浏览器端在从零开始同构开发一中我们已经能实现基本的配置和编译了。接下来我们需要将编译工作工程化。配置作用自动生成自动在引入,。文件内容如下同构开发配置自动刷新这里我们用到的修饰器特性。 从零开始搭建React同构应用(二) 项目工程化(浏览器端) 在从零开始React同构开发(一)中我们已经能实现基本的React配置和编译了。接下来我们需要将编译工作工程...

    wwq0327 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • javascript知识点

    摘要:模块化是随着前端技术的发展,前端代码爆炸式增长后,工程化所采取的必然措施。目前模块化的思想分为和。特别指出,事件不等同于异步,回调也不等同于异步。将会讨论安全的类型检测惰性载入函数冻结对象定时器等话题。 Vue.js 前后端同构方案之准备篇——代码优化 目前 Vue.js 的火爆不亚于当初的 React,本人对写代码有洁癖,代码也是艺术。此篇是准备篇,工欲善其事,必先利其器。我们先在代...

    Karrdy 评论0 收藏0

发表评论

0条评论

paulquei

|高级讲师

TA的文章

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