最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Promise从入门到手写

    正文概述 掘金(寒草)   2021-03-15   470

    Promise从入门到手写

    写作背景

    其实我最开始想写的并不是Promise这篇文章,而是想总结一些axios相关的知识,但是写文章必须有所依据,想要去讲好axios,那么对他的前置知识一定要了解,所以我想不妨把这做成一个系列,于是就有了从PromiseAjax开始,再去讲Axios的打算。其实相信大家做前端开发的一定对Promise不陌生了,所以大家伙也不妨跟着我对Promise这个基础知识进行回顾。 本文将包括:

    • Promise介绍
    • Promise特点
    • Promise使用
    • 根据前文的使用方式和特性,手写Promise

    话不多说,我们直接开始!

    Promise介绍

    Promise起源与用途

    • Promise最早在社区提出和实现,在ES6写入了语言标准。
    • Promise是异步编程的解决方案,比传统的回调函数解决方式更加合理更加强大更加优雅。
    • 语法上: 使用Promiss构造函数对异步操作进行封装以生成Promise实例
    • 功能上: promise对象用来封装一个异步操作并提供统一的API,使得各种异步操作都可以用同样的方式进行处理

    常见的异步编程场景

    • fs文件操作
    require('fs').readFile('./index.html',(err,data)=>{
        // 回调函数
    })
    
    • Ajax操作
    $.get('/api/getUser',(data)=>{
        //handleData();
    })
    
    • 定时器
    setTimeout(() => {
        console.log('timeout');
    },1000);
    

    为什么使用Promise

    • 支持链式调用,将异步操作以同步操作的流程表达出来,可以解决回调地狱问题

    什么是回调地狱?
    回调函数嵌套调用,外部回调函数异步执行结果是嵌套的回调的执行条件

    // 回调地狱典型场景
    asyncFunc1(opt,(...args1) => {
        asyncFunc2(opt,(...args2) => {
            asyncFunc3(opt,(...args3) => {
                asyncFunc4(opt,(...args4) => {
                    //TODO: some opt
                })
            })
        })
    })
    

    回调地狱的缺点:
    不便于阅读,不便于异常处理,不利于身心愉快

    • 指定回调函数的方式更加灵活

    传统方式: 必须在启动异步任务之前指定
    Promise: 可以随时监听异步任务的状态,随时指定回调函数,一个或者多个。

    • Promise提供统一的api,使得控制异步操作更加容易。

    提供了哪些api在后续的使用中会详细阐述

    Promise特点

    Promise的特性

    • 对象状态不受外界影响

    Promise对象代表一个异步操作,有三种状态:pending进行中,fulfilled成功,rejected失败 只有异步操作结束才能改变状态,其他任何操作都不能改变。状态存储在Promise对象的[[PromiseState]]属性中。

    // Promise的两个属性:
    let promiseA = new Promise((resolve,reject)=>{resolve();})
    // 状态对应promiseA的[[PromiseState]]字段。
    let promiseB = new Promise((resolve,reject)=>{resolve(1111);})
    // [[PromiseResult]]的值为 1111,这个字段用于存储 resolve(val)或者reject(val)的参数[val]
    
    • 一旦状态改变,就不会再发生变化

    Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    Promise的缺点

    • 一旦新建,立即执行,无法中途取消
    • Promise内部抛出的错误,无法反映到外部
    • pending状态时无法知道进展到哪一个阶段

    Promise使用

    Promise实例创建

    const promise = new Promise((resolve, reject) => {
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    

    Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

    • resolve函数:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    • reject函数:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    Promise.prototype.then

    Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数
    最终会返回一个新的Promise对象

    promise.then(function(value) {
      // fulfilled状态的处理
    }, function(error) {
      // rejected状态的处理
    });
    

    Promise.prototype.catch

    catch用于处理状态为rejected的回调函数
    最终会返回一个新的Promise对象

    promise.catch((err)=>{
        handleReject();
        //
    })
    

    Promise.resolve

    返回成功或者失败的Promise对象

    let promiseA = Promise.resolve(1);
    // 如果传入的参数为非Promise类型的对象,则返回的结果为成功的Promise对象
    let PromiseB = Promise.resolve(new Promise((resolve,reject)=>{
        reject('err');
    })
    // 如果传入的参数为Promise对象,则参数Promise返回的结果就是 Promise.resolve返回的结果
    // 比如这时return 一个[[PromiseResult]]的值为err的Promise对象
    

    Promise.reject

    返回一个失败的Promise对象

    let PromiseA = Promise.reject(new Promise((resolve,reject)=>{
        resolve('err');
    })
    // 无论传入是啥,就返回一个失败的Promise对象,[[PromiseResult]]的值为 Promise.reject的参数
    

    Promise.all

    接收的参数是由n个Promise对象的数组。

    let promise1 = Promise.resolve(1);
    let promise2 = Promise.resolve(2);
    let promise3 = Promise.reject(3);
    const res = Promise.all([promise1,promise2,promise3]);
    //此时输出为 [[PromiseState]]是rejected,[[PromiseResult]]是3的Promise
    const res = Promise.all([promise1,promise2]);
    //此时输出为 [[PromiseState]]是fulfilled,[[PromiseResult]]是[1,2]的Promise
    

    Promise.race

    接收的参数是由n个Promise对象的数组。

    let promise1 = Promise.resolve(1);
    let promise2 = Promise.resolve(2);
    let promise3 = Promise.reject(3);
    const res = Promise.race([promise1,promise2,promise3]);
    //此时输出为 [[PromiseState]]是fulfilled,[[PromiseResult]]是1的Promise
    

    常见问题

    如何改变Promise对象的状态

    • resolve() // pending => fulfilled
    • reject() // pending => rejected
    • 抛出错误 throw 'err' // pending => rejected

    promise.then() 的返回结果

    • 如果抛出异常,则返回rejected的Promise对象
    • 如果返回的是非promise类型的任意值,则返回状态为resolved的Promise对象
    • 如果返回的是一个新的promise,则该promise的结果会成为新的promise结果

    promise为什么可以链式调用

    因为then,catch,all,race等等所有的promise的api的返回值是新的promise对象。
    所以可以继续打点调用promise的方法,以此种方式将任务串联起来

    promise的异常穿透

    • 当使用promise的then进行链式调用时,可以在最后指定失败的回调
    • 前面的任何错误都会在最后传到失败的回调中去处理
    let p1 = Promise.resolve(1);
    p1.then((value)=>{
        console.log(11);
    }).then((value)=>{
        throw 'err';
    }).then((value)=>{
        console.log(22);
    }).catch(err=>{
        console.log(err);
    })
    //输出: 11  err
    

    中断promise链

    let p1 = Promise.resolve(1);
    p1.then((value)=>{
        console.log(11);
    }).then((value)=>{
        console.log(22);
    }).then((value)=>{
        console.log(33);
    }).catch(err=>{
        console.log(err);
    })
    //输出:11 22 33
    

    那我们怎么去中断这个回调函数的联调呢

    let p1 = Promise.resolve(1);
    p1.then((value)=>{
        console.log(11);
    }).then((value)=>{
        console.log(22);
        return new Promise(()=>{});
    }).then((value)=>{
        console.log(33);
    }).catch(err=>{
        console.log(err);
    })
    //输出:11 22
    

    答案就是返回一个状态为pendingpromise对象

    手写Promise

    代码逐步迭代,可以对照着看,我把注释都写得比较清晰了~

    构造函数实现

    我们第一步首先就是完成Promise的构造函数,构造函数我们简单去想,其实就是接收一个执行器函数,执行器函数有两个参数,这个方法阔以改变Promise对象的状态和结果。ok,说干就干!

    // 万里长城第一步
    function Promise(executor){
        this.promiseState = 'pending';
        this.promiseResult = null;
        const resolve = val => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
            this.promiseState = 'fulfilled';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = val;
        }
    
        const reject = err => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
            this.promiseState = 'rejected';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = err;
        }
        // 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
        try{
            /*
            * 同步执行执行器函数
            * 执行器函数接收两个参数,一个是resolve,一个是reject
            */
            executor(resolve,reject);
        } catch(err) {
            reject(err);
        }
    }
    

    then方法的实现

    首先按照之前说过的,then支持两个参数,分别是成功和失败的回调,而且这两个参数可传可不传。
    其次,因为异步任务的问题,并且支持多个回调,所以我们需要对回调函数采用数组进行存储,所以引入了新的变量,callbackList
    而且我们需要注意then的返回结果也是Promise对象【不记得的话,回头看看~】

    // 万里长城今犹在~
    function Promise(executor){
        //保存promise状态
        this.promiseState = 'pending';
        //保存promise结果
        this.promiseResult = null;
        //用于保存异步回调函数列表
        this.callbackList = [];
        const resolve = val => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
            this.promiseState = 'fulfilled';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = val;
            // 调用成功的回调【callbackList存起来的】
            for(let callback of this.callbackList){
                callback.onResolved(val);
            }
    
        }
    
        const reject = err => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
            this.promiseState = 'rejected';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = err;
            // 调用失败的回调【callbackList存起来的】
            for(let callback of this.callbackList){
                callback.onRejected(err);
            }
        }
        // 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
        try{
            /*
            * 同步执行执行器函数
            * 执行器函数接收两个参数,一个是resolve,一个是reject
            */
            executor(resolve,reject);
        } catch(err) {
            reject(err);
        }
    }
    //then方法
    Promise.prototype.then = function(onResolved,onRejected){
        const self = this;
    
        // then方法会返回Promise
        return new Promise((resolve,reject) => {
            // 对返回值的处理进行封装
            const handleCallback = (callback) => {
                // 如果回调函数中抛出错误,则reject
                try{
                    // 需要依据回调的返回结果确定then方法的返回值
                    // 现在的this会指向return的promise对象,所以使用self
                    const res = callback(self.promiseResult);
                    if(res instanceof Promise){
                        //如果回调返回结果是个Promise
                        res.then(val => {
                            resolve(val);
                        },err => {
                            reject(err);
                        })
                    }else{
                        // 返回结果不是Promise
                        resolve(res);
                    }
                }catch(err){
                    reject(err);
                }
            }
            //调用回调函数
            if(this.promiseState === 'fulfilled'){
                handleCallback(onResolved);
            }
            if(this.promiseState === 'rejected'){
                handleCallback(onRejected);
            }
            /*
            * 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
            * 所以要保存回调函数
            * 因为promise实例阔以指定多个回调,于是采用数组 
            */
            if(this.promiseState === 'pending'){
                this.callbackList.push({
                    onResolved:() => {
                        handleCallback(onResolved); 
                    },
                    onRejected:() => {
                        handleCallback(onRejected);
                    }
                })
            }
        })
    }
    

    catch方法的实现

    我们采用then方法去实现catch方法,但是catch可以处理异常穿透【前面又说哦~】

    // 我想找人陪我去长城玩,哈哈哈哈
    function Promise(executor){
        //保存promise状态
        this.promiseState = 'pending';
        //保存promise结果
        this.promiseResult = null;
        //用于保存异步回调函数列表
        this.callbackList = [];
        const resolve = val => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
            this.promiseState = 'fulfilled';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = val;
            // 调用成功的回调【callbackList存起来的】
            for(let callback of this.callbackList){
                callback.onResolved(val);
            }
    
        }
    
        const reject = err => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
            this.promiseState = 'rejected';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = err;
            // 调用失败的回调【callbackList存起来的】
            for(let callback of this.callbackList){
                callback.onRejected(err);
            }
        }
        // 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
        try{
            /*
            * 同步执行执行器函数
            * 执行器函数接收两个参数,一个是resolve,一个是reject
            */
            executor(resolve,reject);
        } catch(err) {
            reject(err);
        }
    }
    //then方法
    Promise.prototype.then = function(onResolved,onRejected){
        const self = this;
        //处理异常穿透 并且为onResolved,onRejected设置默认值。因为这两个参数可以都不传
        if(typeof onRejected !== 'function'){
            onRejected = err => {
                throw err;
            }
        }
        if(typeof onResolved !== 'function'){
            onResolved = val => val;
        }
        // then方法会返回Promise
        return new Promise((resolve,reject) => {
            // 对返回值的处理进行封装
            const handleCallback = (callback) => {
                // 如果回调函数中抛出错误,则reject
                try{
                    // 需要依据回调的返回结果确定then方法的返回值
                    // 现在的this会指向return的promise对象,所以使用self
                    const res = callback(self.promiseResult);
                    if(res instanceof Promise){
                        //如果回调返回结果是个Promise
                        res.then(val => {
                            resolve(val);
                        },err => {
                            reject(err);
                        })
                    }else{
                        // 返回结果不是Promise
                        resolve(res);
                    }
                }catch(err){
                    reject(err);
                }
            }
            //调用回调函数
            if(this.promiseState === 'fulfilled'){
                handleCallback(onResolved);
            }
            if(this.promiseState === 'rejected'){
                handleCallback(onRejected);
            }
            /*
            * 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
            * 所以要保存回调函数
            * 因为promise实例阔以指定多个回调,于是采用数组 
            */
            if(this.promiseState === 'pending'){
                this.callbackList.push({
                    onResolved:() => {
                        handleCallback(onResolved); 
                    },
                    onRejected:() => {
                        handleCallback(onRejected);
                    }
                })
            }
        })
    }
    
    //catch方法
    Promise.prototype.catch = function(onRejected) {
        //  我们可以直接使用then方法实现
        return this.then(undefined,onRejected);
    }
    

    Promise.resolve方法实现

    简单了,不说啥了

    //resolve方法
    Promise.resolve = function(val) {
        //返回值的情况在前文说过,可以在 Promise的使用一章找到
        return new Promise((resolve,reject)=>{
            if(val instanceof Promise){
                val.then(val => {
                    resolve(val);
                }, err => {
                    reject(err);
                });
            }else{
                resolve(value);
            }
        }) 
    }
    

    Promise.reject方法实现

    更加简单了,不说啥了

     //reject方法
    Promise.reject = function(err) {
        //返回值的情况在前文说过,可以在 Promise的使用一章找到
        return new Promise((resolve,reject)=>{
            reject(err);
        }) 
    }
    

    Promise.all方法实现

    比较简单,回顾之前Promise.all的用法以及返回值,就阔以看懂~

    //可以先去回顾一下all方法的用法
    //all
    Promise.all = function(promiseList) {
        let count = 0;
        let res = [];
        const length = promiseList.length;
        return new Promise((resolve,reject)=>{
            for(let i = 0;i < length; i++){
                promiseList[i].then(val => {
                    count++;
                    res[i] = val;
                    if(count === length){
                        resolve(res);
                    }
                },err => {
                    reject(err);
                });
            }
        }) 
    }
    

    Promise.race方法实现

    比较简单,回顾之前Promise.race的用法以及返回值,就阔以看懂~

    //race
    //要结束了!
    Promise.race = function(promiseList) {
        const length = promiseList.length;
        //谁先完成谁就决定结果!
        return new Promise((resolve,reject)=>{
            for(let i = 0;i < length; i++){
                promiseList[i].then(val => {
                    resolve(val);
                },err => {
                    reject(err);
                });
            }
        }) 
    }
    

    完整代码以及细节处理

    function Promise(executor){
        //保存promise状态
        this.promiseState = 'pending';
        //保存promise结果
        this.promiseResult = null;
        //用于保存异步回调函数列表
        this.callbackList = [];
        const resolve = val => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
            this.promiseState = 'fulfilled';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = val;
            setTimeout(() => {
                // 调用成功的回调【callbackList存起来的】
                for(let callback of this.callbackList){
                    callback.onResolved(val);
                }
            })
        }
    
        const reject = err => {
            // 状态只能修改一次
            if(this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
            this.promiseState = 'rejected';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = err;
            setTimeout(() => {
                // 调用失败的回调【callbackList存起来的】
                for(let callback of this.callbackList){
                    callback.onRejected(err);
                }
            })
        }
        // 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
        try{
            /*
            * 同步执行执行器函数
            * 执行器函数接收两个参数,一个是resolve,一个是reject
            */
            executor(resolve,reject);
        } catch(err) {
            reject(err);
        }
    }
    //then方法
    Promise.prototype.then = function(onResolved,onRejected){
        const self = this;
        //处理异常穿透 并且为onResolved,onRejected设置默认值。因为这两个参数可以都不传
        if(typeof onRejected !== 'function'){
            onRejected = err => {
                throw err;
            }
        }
        if(typeof onResolved !== 'function'){
            onResolved = val => val;
        }
        // then方法会返回Promise
        return new Promise((resolve,reject) => {
            // 对返回值的处理进行封装
            const handleCallback = (callback) => {
                // 如果回调函数中抛出错误,则reject
                try{
                    // 需要依据回调的返回结果确定then方法的返回值
                    // 现在的this会指向return的promise对象,所以使用self
                    const res = callback(self.promiseResult);
                    if(res instanceof Promise){
                        //如果回调返回结果是个Promise
                        res.then(val => {
                            resolve(val);
                        },err => {
                            reject(err);
                        })
                    }else{
                        // 返回结果不是Promise
                        resolve(res);
                    }
                }catch(err){
                    reject(err);
                }
            }
            //调用回调函数
            if(this.promiseState === 'fulfilled'){
                setTimeout(()=>{
                    handleCallback(onResolved);
                })
            }
            if(this.promiseState === 'rejected'){
                setTimeout(()=>{
                    handleCallback(onRejected);
                })
            }
            /*
            * 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
            * 所以要保存回调函数
            * 因为promise实例阔以指定多个回调,于是采用数组 
            */
            if(this.promiseState === 'pending'){
                this.callbackList.push({
                    onResolved:() => {
                        handleCallback(onResolved); 
                    },
                    onRejected:() => {
                        handleCallback(onRejected);
                    }
                })
            }
        })
    }
    //catch方法
    Promise.prototype.catch = function(onRejected) {
        //  我们可以直接使用then方法实现
        return this.then(undefined,onRejected);
    }
    //resolve方法
    Promise.resolve = function(val) {
        //返回值的情况在前文说过,可以在 Promise的使用一章找到
        return new Promise((resolve,reject)=>{
            if(val instanceof Promise){
                val.then(val => {
                    resolve(val);
                }, err => {
                    reject(err);
                });
            }else{
                resolve(value);
            }
        }) 
    }
    
    //reject方法
    Promise.reject = function(err) {
        //返回值的情况在前文说过,可以在 Promise的使用一章找到
        return new Promise((resolve,reject)=>{
            reject(err);
        }) 
    }
    
    //all
    Promise.all = function(promiseList) {
        let count = 0;
        let res = [];
        const length = promiseList.length;
        return new Promise((resolve,reject)=>{
            for(let i = 0;i < length; i++){
                promiseList[i].then(val => {
                    count++;
                    res[i] = val;
                    if(count === length){
                        resolve(res);
                    }
                },err => {
                    reject(err);
                });
            }
        }) 
    }
    
    //race
    Promise.race = function(promiseList) {
        const length = promiseList.length;
        //谁先完成谁就决定结果!
        return new Promise((resolve,reject)=>{
            for(let i = 0;i < length; i++){
                promiseList[i].then(val => {
                    resolve(val);
                },err => {
                    reject(err);
                });
            }
        }) 
    }
    

    至此,我们手写Promise的章节也就结束了~

    结束语

    Promise一词本意是承诺。我以这篇文章作为我新的开始,从基础开始沉淀,拒绝好高骛远。
    我承诺我的信念与追求会始终如一
    我承诺会让自己看遍世间美景
    我承诺我我会一直努力成为优秀的工程师
    我承诺无论结果如何,喜欢的都会努力地勇敢地追求,不留遗憾
    ...
    最后,愿老朽我永远热血,愿世界永远和平
    伙伴们葱呀~ Promise从入门到手写


    起源地下载网 » Promise从入门到手写

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元