研究Promise心得总结与思路总结

简介: 本文总结了Promise的核心功能,包括状态管理、then方法实现以及链式调用和错误处理。

这是这两天研究GoldenaArcher 大佬的[1w6k 字详细讲解] 保姆级一步一步带你实现 Promise 的核心功能后的一些总结和自己的看法,对于promise的理解。

本文的例子代码来自上述文章,在这里表示感谢~~~

首先

根据使用得知,Promise 有着以下几个特点:
Promise 是一个对象
新建 Promise 的时候传进去一个回调函数
回调函数需要接受两个回调函数 resolve 和 reject 作为参数
resolve 在调用成功时使用
reject 在调用失败后使用
resolve 和 reject 会被用来去修改 Promise 的状态——Promise 的初始状态为 pending,表示待定
resolve 会将 pending 状态更改为 fulfilled
reject 会将 pending 状态更改为 rejected
并且,一旦状态确定后,就无法继续被更改

首先,从基本的使用层面上我们可以看出在promise new的时候我们传入了一个函数,函数有两个参数,这两个参数在promise中被分别对应上promise原型上的resolve方法 和reject 方法,所以我们可以将两个参数作为函数来执行。

const promise = new Promise(function (resolve, reject) {
   
  if (successful) {
   
    resolve(value);
  } else {
   
    rejectet(error);
  }
});

解释:

successful是一个变量,当为true的时候返回正确的结果value,调用resolve函数,本来只是一个形参,但是在promise构造函数中它给我们变成了成功函数,在这个函数中,也将pending状态改成了resolved状态。

promise的状态
promise是一个构造函数,它有状态,并且初始状态是pending,只能从pending变成resolved或者rejected;状态变成resolved或者rejected后不能再次进行改变。

promise
在new的时候会传入一个函数,函数有两个参数,分别是以函数调用的形式返回成功结果和失败结果,在promise中,进入constructor会被立即执行

// 常量,用来定义 Promise 中的状态
// 另一方面,使用变量也会有提示,实现起来更加的方便
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseR {
   
  // 定义 Promise 中的状态,默认为pending
  status = PENDING;
  // executor/执行器 是传进 Promise中的回调函数
  constructor(executor) {
   
    // 这个回调函数会被立即执行,判断是否应该对状态进行更改
    executor(this.resolve, this.reject);
  }
   // 实现 resolve & reject,用来更改 Promise 的状态
  // 使用箭头函数可以减少 this 指向造成的问题,将其绑定在 Promise 的实例对象
  resolve = () => {
   
    // 只有当状态是 pending 的时候才能修改状态
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
  };
  reject = () => {
   
    // 只有当状态是 pending 的时候才能修改状态
    if (this.status !== PENDING) return;
    this.status = REJECTED;
  };
  }

在constructor中有我们传入的回调函数executor,相当于我们传入回调函数的两个参数,变成了promise类上的resolve函数和reject函数,当我们执行的时候返回了我们结果和改变了promise的状态。

上面代码只是将状态做了改变,规定状态只能从pending到resolved或者rejected,并且改变之后不能再次改变。

其次
当我们new promise后返回的是一个promise的对象,我们会使用then来接受正确的结果,then函数的简单实用:

const existedFile = readFile('./test.txt');
existedFile.then(
  (data) => {
   
    console.log('content: ', Buffer.from(data).toString());
  },
  (error) => {
   
    console.log(error);
  }
);

readFile读取文件是一个用promise封装好的异步方法,所以有then方法,then方法传入了两个参数,分别是两个函数,第一个函数是的形参是正确的结果,第二个函数的形参是报错信息。

由此可见,then 函数有以下的几个特性:

then 函数接收两个参数

第一个在异步函数操作成功时调用

第二个在异步函数操作失败时调用

then 函数必须要有能够分析 Promise 状态的能力,再根据状态去调用 成功回调函数 或是 失败回调函数

then 方法是被定义在原型对象上的:Promise.prototype.then()

then 的成功函数会接受一个成功后的值作为参数,失败后也会接受一个失败的原因作为参数

then

顾名思义,其实在then中传入两个函数,就是为了在promise上的then方法中使用,将正确的结果以回调的形式传出去,方便再then链式调用中使用。所以下面是简单实现,将传入的两个参数分别执行并传入结果值this.value,这个this.value实在resolve方法传入的,然后保存在了promise原型上,在这里将结果以回调函数的形式返回。

// 接收两个回调函数,前者在状态为 fulfilled 时使用,后者在状态为 rejected 时使用
  then(successCB, failCB) {
   
    // 判断状态,根据状态去调用合适的回调函数,并且传入对应的值
    // value 和 reason 在 resolve 和 reject 部分已经保存到实例属性上了
    if (this.status === FULFILLED) {
   
      successCB(this.value);
    } else if (this.status === REJECTED) {
   
      failCB(this.reason);
    }
  }

在promise中是异步的,所以有时候会等待执行
添加异步逻辑
通常情况下会在 Promise 中被调用的是异步函数,下面就是使用 setTimeout 实现的,一个简单的异步函数的例子:

const asyncPromise = new PromiseR((resolve, reject) => {
   
  setTimeout(() => {
   
    resolve('success');
  }, 2000);
});

asyncPromise.then((value) => {
   
  console.log('ln90', value); // 没有输出结果
});

因为在then中并没有对promise状态进行更改,一直是pending

if (this.status === FULFILLED) {
   
  successCB(this.value);
} else if (this.status === REJECTED) {
   
  failCB(this.reason);
}

主线程对于函数的调用也是同步的,它会执行对 PromiseR 对象的实例,接着直接调用 then 函数。此时的 setTimeout 会在 Web API 中被调用,一直到约两秒钟后,主线程执行完毕了,setTimeout 的时间到了,它才会通过 消息队列 与 事件循环 机制被压到 执行栈 中。

这个时候就需要将then中传递过来的函数保存起来

  successCB = [];
  failCB = [];
 resolve = (value) => {
   
    // 省略其他函数
    // 调用成功函数
    this.successCB && this.successCB(this.value);
  };

  reject = (reason) => {
   
    // 省略其他函数
    // 调用失败函数
    this.failCB && this.failCB(this.reason);
  };

 then(successCB, failCB) {
   
    // 判断状态,根据状态去调用合适的回调函数,并且传入对应的值
    // value 和 reason 在 resolve 和 reject 部分已经保存到实例属性上了
    if (this.status === FULFILLED) {
   
      successCB(this.value);
    } else if (this.status === REJECTED) {
   
      failCB(this.reason);
    }
    // 函数还没有执行完毕,只能等待
    else {
   
      // 将两个回调函数的值存储起来
      this.successCB = successCB;
      this.failCB = failCB;
    }
  }

在这里增加了一个类似异步的操作,使用定时器来模拟异步操作,然后再then中判断状态,如果状态已经改变,说明可以直接返回,如果没有说明走的判断状态的else,将传进来的两个函数保存了起来,等待异步完成之后,在resolve和reject这两个函数中执行回调函数successCB、failCB 。

const asyncPromise = new PromiseR((resolve, reject) => {
   
  setTimeout(() => {
   
    resolve('success');
  }, 2000);
});

asyncPromise.then((value) => {
   
  console.log('ln103', value);
});

这样等待定时器执行完,就可以then中获取到success了

多次then调用,不是链式调用

  successCB = [];
  failCB = [];

  resolve = (value) => {
   
    // 这里改为使用 shift 去将值弹出
    // 弹出的值本身就是一个函数,因此可以直接调用
    while (this.successCB.length) {
   
      this.successCB.shift()(this.value);
    }
  };
  // 修改参数,让 reject 接收成功后传来的原因
  reject = (reason) => {
   
    // 这里改为使用 shift 去将值弹出
    // 弹出的值本身就是一个函数,因此可以直接调用
    while (this.failCB.length) {
   
      this.failCB.shift()(this.reason);
    }
  };

  then(successCB, failCB) {
   
      resolve 和 reject 部分已经保存到实例属性上了
    if (this.status === FULFILLED) {
   
      successCB(this.value);
    } else if (this.status === REJECTED) {
   
      failCB(this.reason);
    }
    else {
   
      // 数组需要通过push将两个回调函数的值存储起来
      this.successCB.push(successCB);
      this.failCB.push(failCB);
    }
  }

这里做了一个很巧妙的处理,将所以待执行的then中成功函数和失败函数都收集起来,在resolve和reject中循环执行第一个,一直执行第一个,因为shift数组方法返回的是删除项,这一项是个函数,随之调用,将value传入。

多次调用 then 的测试

const multiplePromise = new PromiseR((resolve, reject) => {
   
  setTimeout(() => {
   
    console.log('success');
    resolve('success');
  }, 2000);
});
multiplePromise.then((value) => {
   
  console.log('success1'); // success1
});
multiplePromise.then((value) => {
   
  console.log('success2'); // success2
});

将两次then中的成功函数push到了this.successCB中,在异步执行完后,resolve函数中传入value ==success,随后循环执行最先进入的函数,先打印success1随后打印succcess2

实现 then 方法的链式调用

对于这个功能的视线,有以下几个需求:

实现 thenable 功能

也就是在不考虑其他功能的前提下,先完成链式调用的嵌套

判断返回的应该是一个值,还是应该是 Promise 对象

这还需要判断 Promise 对象返回的结果再决定调用 resolve 还是 reject

使用then().then()…的形式,必须上一个then函数调用的返回值属于promsie对象,并且它的状态也是resolved才能走为我们下一个then

class PromiseR {
   
  then(successCB, failCB) {
   
    // 新建一个Promise对象,否则没有东西可以被返回
    const thenablePromise = new PromiseR(() => {
   
      // promise 对象需要接受一个立即执行函数
      // 本身 then 之中的逻辑也是需要被立即执行的
      // 因此可以将原本的逻辑作为 立即执行函数 executor 传入到 Promise 对象中
      if (this.status === FULFILLED) {
   
        const thenableValue = successCB(this.value);
        resolve(thenableValue);
      } else if (this.status === REJECTED) {
   
        const thenableReason = failCB(this.reason);
        reject(thenableReason);
      } else {
   
        this.successCB.push(successCB);
        this.failCB.push(failCB);
      }
    });

    return thenablePromise;
  }
}

返回一个promise对象,this指向当前的promise对象,用当前的promise的状态去做判断

then中返回 Promise 代码实现

class PromiseR {
   
  then(successCB, failCB) {
   
    const thenablePromise = new PromiseR((resolve, reject) => {
   
      if (this.status === FULFILLED) {
   
        const thenableValue = successCB(this.value);
        resolvePromise(thenableValue, resolve, reject);
      } else if (this.status === REJECTED) {
   
        const thenableReason = failCB(this.reason);
        resolvePromise(thenableReason, resolve, reject);
      } else {
   
        this.successCB.push(successCB);
        this.failCB.push(failCB);
      }
    });

    return thenablePromise;
  }
}

// 这段函数会被调用2次,所以单独抽离封装到外部会比较好
// 这里的逻辑主要就负责判断是判断传进来的 thenable 对象是 Promise吗,是的话调用 then 函数去处理,否则 直接返回值
const resolvePromise = (thenablePromise, resolve, reject) => {
   
  if (thenablePromise instanceof PromiseR) {
   
    thenablePromise.then(resolve, reject);
  } else {
   
    resolve(thenablePromise);
  }
};

代码测试:

// chaining then
const thenablePromise = new PromiseR((resolve, reject) => {
   
  resolve('success');
});

const otherPromise = () =>
  new PromiseR((resolve, reject) => {
   
    setTimeout(() => {
   
      resolve('otherPromise');
    }, 2000);
  });

thenablePromise
  .then((value) => {
   
    console.log(value, new Date());
    return otherPromise();
  })
  .then((value) => {
   
    console.log(value, new Date());
  });

在thenablePromise的then中返回了一个promise对象,所以还是可以继续then

[nodemon] restarting due to changes...
[nodemon] starting `node 03.handwritePromise.js`
success 2021-06-25T07:55:56.594Z
otherPromise 2021-06-25T07:55:58.611Z
[nodemon] clean exit - waiting for changes before restart

时间戳有 2s 左右的差异

Promise 的错误处理,加上try…catch来进行捕获错误,reject()返回出去,改变promise状态;

promise总体还是一直在搞回调函数,调用回调函数,改变promsie状态,返回结果,then方法接受结果调用传入函数,传入正确结果。

目录
相关文章
|
7月前
|
存储 前端开发 JavaScript
【面试题】Promise只会概念远远不够,还需这17道题目巩固!
【面试题】Promise只会概念远远不够,还需这17道题目巩固!
|
7月前
|
前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)
|
7月前
|
存储 运维 前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
|
6月前
|
存储 前端开发 API
技术笔记:Promise的原理探究及手写Promise
技术笔记:Promise的原理探究及手写Promise
37 0
|
设计模式 前端开发
换个角度理解Promise
如下是一点白话文的理解,清晰易懂
55 0
|
存储 编译器 C语言
【C】函数真的难嘛?其实一点也不难,原理很简单。
# 什么是函数 程序是由多个零件组合而成的,而函数就是这种“零件”的一个较小单位。 ## main函数和库函数 C语言程序中,main函数是必不可少的。程序运行的时候,会执行main函数的主题部分。main函数中使用了printf、scanf、puts等函数。由C语言提供的这些为数众多的函数称为库函数。 ## 什么是函数 当然,我们也可以自己创建函数。而实际上,我们也必须亲自动手创建各种函数。下面我们来自己创建一个简单的函数。 创建一个函数,接收两个整数参数,返回较大整数的值。 printf函数和scanf函数等创建得比较好得函数,即使不知道其内容,只要了解使用方法,也可以轻松使用。 ## 函
|
前端开发
前端学习案例12-promise源码实现4
前端学习案例12-promise源码实现4
76 0
前端学习案例12-promise源码实现4
|
前端开发
前端学习案例12-promise源码实现4
前端学习案例12-promise源码实现4
53 0
前端学习案例12-promise源码实现4
|
前端开发
前端学习案例13-promise源码实现5
前端学习案例13-promise源码实现5
67 0
前端学习案例13-promise源码实现5
|
前端开发
前端学习案例11-promise源码实现2
前端学习案例11-promise源码实现2
71 0
前端学习案例11-promise源码实现2