首页 > 其他分享 >手写Promise

手写Promise

时间:2022-09-29 15:14:23浏览次数:80  
标签:function resolve state Promise reject 手写 onRejected

什么是Promise?

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise出现之前都是通过回调函数来实现,回调函数本身没有问题,但是嵌套层级过深,很容易掉进回调地狱

const fs = require('fs');
fs.readFile('1.txt', (error,data) => {
    fs.readFile('2.txt', (error,data) => {
        fs.readFile('3.txt', (error,data) => {
            //你的代码
        });
    });
});

Promise的出现正是为了解决这个痛点,我们可以把上面的回调嵌套用Promise改写一下:

const readFile = function(fileName){
    return new Promise((resolve, reject)=>{
        fs.readFile(fileName, (error, data)=>{
            if(error){
                reject(error)
            } else {
                resolve(data)
            }
        })
    })
}
readFile('1.txt')
    .then(data => {
        return readFile('2.txt');
    }).then(data => {
        return readFile('3.txt');
    }).then(data => {
        //...
    });

Promise基本结构:
回顾一下,我们平常使用promise的方式:

var p = new Promise(function(resolve, reject){
    console.log('执行')
    setTimeout(function(){
        resolve(2)
    }, 1000)
})
//then方法返回的也是一个promise对象
p.then(function(res){
    console.log('res',res)
},function(err){
    console.log('err',err)
})

首先看出来,Promise是通过构造函数实例化一个对象,然后通过实例对象上的then方法,来处理异步返回的结果。同时,promise/A+规范规定了:

promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;
一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因
    function resolve(value) {} //成功时的回调
    function reject(reason) {} //失败时的回调
}

Promise.prototype.then = function (onFulfilled, onRejected) {
};

module.exports = Promise;

当我们实例化Promise时,构造函数会马上调用传入的执行函数executor,我们可以试一下:

let p = new Promise((resolve, reject) => {
    console.log('执行了');
});

因此在Promise中构造函数立马执行,同时将resolve函数和reject函数作为参数传入:

function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因
    function resolve(value) {}
    function reject(reason) {}
    executor(resolve, reject)

但是executor也会可能存在异常,因此通过try/catch来捕获一下异常情况:

try {
    executor(resolve, reject);
} catch (e) {
    reject(e);
}

不可变
promise/A+规范中规定,当Promise对象已经由等待态(Pending)改变为执行态(Fulfilled)或者拒绝态(Rejected)后,就不能再次更改状态,且终值也不可改变。因此我们在回调函数resolve和reject中判断,只能是pending状态的时候才能更改状态

function resolve (value) {
    if(_this.state === PENDING) {
        _this.state = FULFILLED;
        _this.value = value;
    }
}
//失败时的回调
function reject (reason) {
    if(_this.state === PENDING) {
        _this.state = REJECTED;
        _this.reason = reason;
    }
}

then实现
当Promise的状态改变之后,不管成功还是失败,都会触发then回调函数。因此,then的实现也很简单,就是根据状态的不同,来调用不同处理终值的函数。

Promise.prototype.then = function (onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        typeof onFulfilled === 'function' && onFulfilled(this.value)
    }
    if(this.state === REJECTED){
         typeof onRejected === 'function' && onRejected(this.reason)
    }
};

在规范中也说了,onFulfilled和onRejected是可选的,因此我们对两个值进行一下类型的判断:

onFulfilled 和 onRejected 都是可选参数。如果 onFulfilled 不是函数,其必须被忽略。如果 onRejected 不是函数,其必须被忽略
代码写到这里,貌似该有的实现方式都有了,我们来写个demo测试一下:

var myP = new Promise(function(resolve, reject){
    console.log('执行')
    setTimeout(function(){
        reject(3)
    }, 1000)
});
myP.then(function(res){
    console.log(res)
},function(err){
    console.log(err)
});

很遗憾,运行起来我们发现只打印了构造函数中的执行,下面的then函数根本都没有执行。我们整理一下代码的运行流程:

当then里面函数运行时,resolve由于是异步执行的,还没有来得及修改state,此时还是PENDING状态;因此我们需要对异步的情况做一下处理。
支持异步
参考发布订阅模式,如果当前是PENDING状态,就把回调函数寄存到一个数组中,当状态发生改变时,去数组中取出回调函数,因此先定义下变量

function Promise(executor) {
	this.onFulfilled = [];//存成功的回调
	this.onRejected = [];//存失败的回调
}

这样,当then方法执行时,如果还是PENDING状态时,不立马执行回调函数,而是先存到数组中

Promise.prototype.then = function (onFulfilled, onRejected) {
    if (this.state === 'FULFILLED') {
        typeof onFulfilled === 'function' && onFulfilled(this.value)
    }
    if (this.state === 'REJECTED') {
        typeof onRejected === 'function' && onRejected(this.reason)
    }
    if (this.state === PENDING) {
        typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
        typeof onRejected === 'function' && this.onRejected.push(onRejected)
    }
}

存储起来后,当resolve或者reject异步执行的时候就可以来调用了:

function resolve(value) {
    if(_this.state === PENDING){
        _this.state = FULFILLED
        _this.value = value
        _this.onFulfilled.forEach(fn => fn(value))
    }
}
function reject(reason) {
    if(_this.state === PENDING){
        _this.state = REJECTED
        _this.reason = reason
        _this.onRejected.forEach(fn => fn(reason))
    }
}

有童鞋可能会提出疑问了,为什么这边onFulfilled和onRejected要存在数组中,直接用一个变量接收不是也可以么?下面看一个例子:

var p = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve(4)
    }, 0)
})
p.then((res)=>{
    //4 res
    console.log(res, 'res')
})
p.then((res1)=>{
    //4 res1
    console.log(res1, 'res1')
})

我们分别调用了两次then,如果是一个变量的话,最后肯定只会运行后一个then,把之前的覆盖了,如果是数组的话,两个then都能正常运行。

至此,我们运行demo,就能如愿以偿的看到运行结果了;一个四十行左右的简单Promise垫片就此完成了。这里贴一下完整的代码:

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
export function MyPromise(executor) {
    let that = this;
    this.state = PENDING;  //状态
    this.value = undefined; //成功返回的结果
    this.reason = undefined; //失败的原因
    
    this.onFulfilledCallback = []; //存成功时需要执行的回调函数
    this.onRejectedCallback = []; //存失败时需要执行的回调函数

    //成功时的回调
    function resolve (value) {
        if (that.state === PENDING) {
            that.state = FULFILLED; //状态改变
            that.value = value;
            
            //异步执行,同步任务执行完以后才执行,在这里执行由于state值没来得及修改而没有执行的回调函数
            setTimeout(() => {
                that.onFulfilledCallback.forEach(callback => callback(value));
            });
        }
    }

    //失败时的回调
    function reject (reason) {
        if (that.state === PENDING) {
            that.state = REJECTED;
            that.reason = reason;
         
            //异步执行,同步任务执行完以后才执行,在这里执行由于state值没来得及修改而没有执行的回调函数
            setTimeout(() => {
                that.onRejectedCallback.forEach(callback => callback(reason));
            });
        }
    }

    try { 
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }

    MyPromise.prototype.then = function (onFulfilled, onRejected) {
          //判断回调函数参数,如果不是函数设置默认值
          if(typeof onFulfilled !== 'function') {
               onFulfilled = value => value;
          }
          if(typeof onRejected !== 'function') {
               onRejected = reason => {
                   throw reason; 
             }
          }
          //如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e。
        if (this.state === FULFILLED) {
            let promise1 = new MyPromise(function (resolve, reject) {
                try {
                    onFulfilled(this.value);
                } catch (error) {
                    reject(error);
                }
            });
            return promise;
        }

        if (this.state === REJECTED) {
            let promise2 = new MyPromise(function (resolve, reject) {
                try {
                    onRejected(this.reason);
                } catch (error) {
                    reject(error);
                }
            });
            return promise2;
        }
        
        //当走到then时,如果状态为PENDING,先不执行回调,将它们存起来,在异步回调中去调用
        if (_this.state === PENDING) {
            typeof onFulfilled === 'function' && this.onFulfilledCallback.push(onFulfilled);
            typeof onRejected === 'function' && this.onRejectedCallback.push(onRejected);
        }
    };
    
    MyPromise.prototype.catch = function (onRejected) {
        //catch函数为then函数的特例,第一个参数为undefined或者null
        return this.then(undefined, onRejected);
    };

    //传入promise数组,
    //1.每当遍历结果是成功时,用计数器记录,当计数器当值等于数组长度,则全部成功,返回成功状态
    //2.如果当数组中任意一个promise的执行结果是rejected时,直接返回失败的状态
     MyPromise.all = (promises) => {
        return new Promise((resolve, reject) => {
            //计数器
            let count = 0;
            //存放结果
            let arr = [];
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    count++;
                    arr[i] = v;
                    if (count === promises.length) {
                        resolve(arr);
                     }
                }, r => reject(r));
            }
        });
    };
     //谁先执行完就返回谁的结果和状态,参数也是promise组成的数组
    MyPromise.race = (promises) => {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < promises; i++) {
                promises[i].then(v => {
                   //谁的回调先执行就先返回谁
                    resolve(v);
                }, r => reject(r));
            }
        });
    };
} 

本文抄写自网络文章,抄写此文章仅为个人收藏,分享知识,如有侵权,请联系博主进行删除。
原文作者:谢小飞
原文地址:https://zhuanlan.zhihu.com/p/144058361

标签:function,resolve,state,Promise,reject,手写,onRejected
From: https://www.cnblogs.com/sweetdreamkx/p/16730121.html

相关文章

  • Js手写面试题5-Promise
    Promise❓有任何疑问都可以私信我解答⚡仓库地址:https://gitee.com/super_li_yu/promise......
  • 手写一个webpack插件
    前言前端性能优化是一个老生常谈的话题,关于性能优化的技术文档和书籍都特别多。如果大家想深入学习前端性能优化相关内容,有以下推荐雅虎军规35条某东上搜“前端性能优......
  • 手写编程语言-递归函数是如何实现的?
    前言本篇文章主要是记录一下在GScript中实现递归调用时所遇到的坑,类似的问题在中文互联网上我几乎没有找到相关的内容,所以还是很有必要记录一下。在开始之前还是简单......
  • Promises/A+知识及其实现过程
    promise核心要点Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。待定(pending):初始状态,既没有被兑现,也没有被拒绝。已成......
  • JavaScript中Promise详解
    概述Promise(期约)对象用于表示一个异步操作的最终完成(或失败)及其结果值。Promise的使用Promise创建时需传入一个执行器函数(excutor)接受两个参数,第一个参数是内部定......
  • 创建小程序打开报错--Unhandled promise rejection
    遇到的问题:刚创建完小程序打开,发现抱错--Unhandledpromiserejection(env:Windows,mp,1.05.2204250;lib:2.26.1)原因:版本太高解决办法:在右上角点击详情,找到......
  • 照猫画虎之实现Promise
     //promise(丑陋的)classUglyPromise{constructor(callback){this.status='pending'this.value=undefinedthis.sucessCb=[]this.fail......
  • JavaScript手写函数
     //url的queryString转成对象functionqueryStr2Obj(url){constquery={};constsearch=url.split('?')[1];if(!search){return{}......
  • 手写vue-router核心原理
    最近也在观察vue3新特性,抽空玩一玩嵌套路由的vue-router,直接上代码项目目录结构代码展示app.vue<template><divid="app"><div><router-linkto="/"......
  • 实现 Promise.all,所有 Promise 成功返回成功,一个失败返回失败
    首先我们先创建一个resolve和reject的promise函数constpromiseResove=(promiseResolveSecond=function(n=0){returnnewPromise(function(resolve,reject){......