Promise 实现详解

这篇文章通过自己实现一个Promise来加深对promise的理解,具体如何实现符合promise规范的代码,可以查看Promises/A+

梳理 Promise 功能

1、Promise是一个构造函数,它接收一个执行函数作为参数,执行函数里面包含resolvereject两个方法;

2、调用resolve方法表示成功,reject方法表示失败,结果会在实例的then函数中以参数函数的方式拿到;

3、then是实例方法,参数包含成功的回调函数和失败的回调函数,只用resolvereject执行后方法才会执行;

状态变化

promise里面的状态决定了当前能执行什么方法,先定义三个变量来保存状态:

const PENDING_STATUS = "pending"; // 等待,用户还没调用resolve或者reject

const FULFILLED_STATUS = "fulfilled"; // 成功,用户调用了resolve

const REJECTED_STATUS = "rejected"; // 失败,用户调用了reject

有状态就会有出现状态的原因了,后面还需要把这些原因传给then方法,我们先创建构造函数:

function Promise(executor) {

this.status = PENDING_STATUS;

this.value = null; // 保存resolve方法的参数

this.reason = null;// 保存reject方法的参数

}

定义好了状态我们还要知道什么时候去修改它,我们知道executor执行函数接受resolvereject两个方法,这两个方法执行的时候就是我们修改状态的时候了:

function Promise(executor) {

this.status = PENDING_STATUS;

this.value = null; // 保存resolve方法的参数

this.reason = null;// 保存reject方法的参数

const resolve = (value)=>{

// 如果状态不是 pending 返回

if (this.status !== PENDING_STATUS) return;

// 先保存参数

this.value = value;

// 改变状态

this.status = FULFILLED_STATUS;

}

const reject = (reason)=>{

if (this.status !== PENDING_STATUS) return;

this.reason = reason;

this.status = REJECTED_STATUS;

}

try {

executor(resolve, reject)

} catch (error) {

reject(error)

}

}

then 方法

我们知道then方法接受两个参数,成功后的函数回调和失败后的函数回调,那么我们可以在函数里面判断当前状态,并执行对应的方法:

Promise.prototype.then = function (onfulfilled, onrejected){

if (this.status === FULFILLED_STATUS){}

if (this.status === REJECTED_STATUS){}

if (this.status === PENDING_STATUS){}

}

根据状态去判断执行onfulfilled还是onrejected方法比较容易,但是如果状态仍处于 pending,就不能直接执行onfulfilledonrejected了,我们可以利用发布订阅的方式将方法缓存起来,直到resolvereject被调用才取出来执行,我们先在构造函数里面定义onResolvedCallbackonRejectedCallback两个数组:

function Promise(executor) {

...

this.onResolvedCallback = [];

this.onRejectedCallback = [];

...

}

then方法里面,我们在pending状态时先将执行函数保存起来:

Promise.prototype.then = function (onfulfilled, onrejected){

if (this.status === FULFILLED_STATUS){

onfulfilled(this.value);

}

if (this.status === REJECTED_STATUS){

onrejected(this.reason);

}

if (this.status === PENDING_STATUS){

this.onResolvedCallback.push(()=>{

onfulfilled(this.value);

})

this.onRejectedCallback.push(()=>{

onrejected(this.reason);

})

}

}

resolvereject函数中取出并执行:

const resolve = (value)=>{

if (this.status !== PENDING_STATUS) return;

this.value = value;

this.status = FULFILLED_STATUS;

// 取出缓存的方法并执行

this.onResolvedCallback.forEach(fn=>fn());

}

链式调用

promise方法可以实现链式调用,上一个函数onfulfilled的返回结果,可以在下一次then函数中被获取:

new Promise((resolve)=>resolve("result1"))

.then(r=>`result2`)

.then(r=>`result3`)

.then(r=>`result4`);

可以看到then方法可以不断的被链式调用,这说明then方法也返回了一个promise实例:

Promise.prototype.then = function (onfulfilled, onrejected){

let promise = new Promise((resolve,reject)=>{

if (this.status === FULFILLED_STATUS) {

onfulfilled(this.value);

}

if (this.status === REJECTED_STATUS) {

onrejected(this.reason);

}

if (this.status === PENDING_STATUS) {

this.onResolvedCallback.push(() => {

onfulfilled(this.value);

})

this.onRejectedCallback.push(() => {

onrejected(this.reason);

})

}

});

return promise;

}

但是onfulfilledonrejected的返回结果,有可能是普通的值或者是promise,所以我们也要处理下返回的值:

let promise = new Promise((resolve,reject)=>{

if(this.status === FULFILLED_STATUS){

let value = onfulfilled(this.value);

// 函数在setTimeout里面执行,这样能保证拿到返回的promise变量

setTimeout(()=>{

try {

resolvePromise(promise,value,resolve,reject);

} catch (error) {

onrejected(error)

}

})

}

if(this.status === REJECTED_STATUS){

let value = onrejected(this.reason);

setTimeout(() => resolvePromise(promise, value, resolve, reject));

}

if(this.status === PENDING_STATUS){

this.onResolvedCallback.push(()=>{

let value = onfulfilled(this.value);

setTimeout(() => {

try {

resolvePromise(promise, value, resolve, reject);

} catch (error) {

onrejected(error)

}

})

});

this.onRejectedCallback.push(()=>{

let value = onrejected(this.reason);

setTimeout(() => resolvePromise(promise, value, resolve, reject));

})

}

});

resolvePromise函数我们要做的处理是判断返回的value值,这里有几个判断需要注意:

  • value 跟返回的 promise 相同,循环引用直接抛出错误
  • value 是 Promise 实例,在 then 方法里面执行 resolve
  • 直接resolve value
function resolvePromise(promise,value,resolve,reject){

if (promise === value) throw new Error("循环引用");

if(value instanceof Promise){

let then = value.then;

try {

then.call(value,(result)=>{

resolve(result);

},(error)=>{

reject(error);

})

} catch (error) {

reject(error)

}

}else{

resolve(value);

}

}

值的穿透

Promise支持一种比较奇葩的写法:

let p = new Promise((resolve)=>resolve("result"));

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

遇到这种写法,需要在then函数里面提前做判断:

Promise.prototype.then = function(onfulfilled,onrejected){

onfulfilled = typeof onfulfilled === "function" ? onfulfilled : (val)=>val;

onrejected = typeof onrejected === "function" ? onrejected : (error)=>error;

...

}

catch

Promise.prototype.catch = function (errorCallback) {

return this.then(null, errorCallback)

}

resolve 和 reject

Promise.resolve = function (value) {

return new Promise((resolve) => resolve(value))

}

Promise.reject = function (value) {

return new Promise((resolve, reject) => reject(value))

}

Promise.all

all方法接受一个数组,我们需要判断数组里面的Promise方法,只有在then方法执行后才算完成,可以定义一个完成变量保存完成的数量,当完成数量等于数组长度时,才resolve最终的结果

Promise.all = function (values) {

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

let result = [];

let resultIndex = 0; // 执行完成的数量

let len = values.length;

function processData(index, value) {

result[index] = value;

resultIndex++; // 执行完成后加一

if (resultIndex === len) {

// 最终结果

resolve(result);

}

}

for (let i = 0; i < len; i++) {

let value = values[i];

let then = value.then;

if (then && (typeof then === "function")) {

then.call(value, result => {

processData(i, result)

}, reject => {

processData(i, reject)

})

} else {

processData(i, value)

}

}

})

}

race

race执行返回最快完成的结果,所以当then函数执行后直接执行resolve返回结果:

Promise.race = function (values) {

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

for (let i = 0; i < values.length; i++) {

let value = values[i];

let then = value.then;

if (then && (typeof then === "function")) {

then.call(value, result => {

resolve(result)

}, reject)

} else {

resolve(value);

break;

}

}

})

}
原文链接:segmentfault.com

上一篇:js如何获取上个月第一天和最后一天
下一篇:mnl.js —— JavaScript 函数仿生命名法

相关推荐

  • 🔥 Promise|async|Generator 实现&amp;原理大解析 | 9k字

    笔者刚接触async/await时,就被其暂停执行的特性吸引了,心想在没有原生API支持的情况下,await居然能挂起当前方法,实现暂停执行,我感到十分好奇。好奇心驱使我一层一层剥开有关JS异步编程的...

    6 个月前
  • (详解)从浏览器输入 URL 到页面展示过程

    引言 对于面试常问的从浏览器输入 URL 到页面渲染过程发生了什么?,我想大家都或多或少能说出一二。但是,其实这个问题很有深度,而你是否回答的有深度,在很大程度上会影响到面试官对你的印象。

    6 个月前
  • 面试题1:Promise递归实现拉取数据

    题目:请用promise递归实现拉取100条数据,每次拉取20条,结束条件为当次拉取不足20条或者已经拉取100条数据 let mockData = [ 0, 1, 2, 3, 4, ...

    5 个月前
  • 面试必备之详解JS数字精度

    0.前言 最近在看计算机组成原理的浮点数部分,突然想起之前看过的一道快手面试题 为什么JS中0.1+0.2不等于0.3,应该如何解决? 这里我们可以借这道题来说一下JS的精度问题 1.JS数的...

    8 个月前
  • 面试官要求我们手动实现 Promise.all

    情景: 最近面试,有两次被问到手动实现 Promise.all,不幸的是我都没把这题做好。因为我没有去准备这个,我不知道手动实现已有的 API 有什么意义。 但是为了防止以后还会遇到此类题,还是记录下...

    4 个月前
  • 面试官你来,130行带你手写完整Promise

    大家好,我是雷锋蜀黍。一直在某些面试宝典看到面试官要你手撸一个promise,今天天气这么好,不如我们来搞一搞。(PS:从未看过别人的实现,本文更像是记录一个思考的过程) 最终的代码完全符合Promi...

    6 个月前
  • 阅读Promise A+规范

    本文主要是PromiseA+规范的翻译加上个人的理解。 1 什么是Promise A promise represents the eventual result of an asynchronou...

    3 年前
  • 重学前端-Promise用法详解

    前言 在JavaScript的世界中,Promise毫无疑问是里程碑式的一个变革,它不仅解决了异步编程中,回调函数嵌套过深的问题,还为Async/Await奠定了基础。

    1 个月前
  • 通过npm引用的vue组件使用详解

    什么是组件:组件是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能。

    4 年前
  • 通过koa2和Promise.race()构造一个超时取消的ajax。

    MDN上说: 你可以使用AbortController.AbortController()构造函数创建一个新的AbortController对象。 使用AbortSignal 对象完成与DOM请求的...

    2 年前

官方社区

扫码加入 JavaScript 社区