今天看啥  ›  专栏  ›  寒草

Promise从入门到手写

寒草  · 掘金  ·  · 2021-03-15 09:40
阅读 48

Promise从入门到手写

作者:寒草
微信:hancao97
介绍:一个不一样的北漂程序员,欢迎加微信批评指正交流技术

写作背景

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

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

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

Promise介绍

Promise起源与用途

  • Promise最早在社区提出和实现,在ES6写入了语言标准。

tip: ES6即ECMA-262第六版,这一版包含了这个规范有史以来最重要的一批增强特性,Javascript是ECMA-262规范的实现

  • 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]
复制代码

tips: Promise本意是承诺,也是因为此原因,想一想这是多么浪漫的名字。

  • 一旦状态改变,就不会再发生变化

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对象

tip: then中的回调函数是可选的,不一定要提供

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对象的数组。

tips: 返回结果是新的promise,只有所有的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对象的数组。

tips: 返回结果是新的promise,第一个完成的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,说干就干!

tips:

  1. 注意!throw err也可以修改Promise的状态与结果!
  2. promise的状态只能修改一次,需要做限制
// 万里长城第一步
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);
            });
        }
    }) 
}
复制代码

完整代码以及细节处理

tips: 细节回调函数是异步的 我们使用setTimeout进行包裹

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的章节也就结束了~

结束语

tips:其实我真的很想吐槽自己,为什么老是在博客结尾要么打鸡血,要么说鸡汤,哈哈

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




原文地址:访问原文地址
快照地址: 访问文章快照