Promise和异步编程

245 阅读9分钟

个人感悟

    经过一个星期业务的摧残,就算学习完了拉勾的课程,也没有办法动手,趁着周六总算可以把手写Promise的心愿完成了,所有学习内容都在这回代码注释里面了.前面一大段是总的开发过程 然后在代码里注释相应代码的作用.终于掌握了promise,拉勾这个课程俺还是觉得蛮好的解决了面试有关Promise的痛点,以后这方面的面试题也都不用担心了。

    在撸代码前先把异步编程相关知识点补上。

异步编程

    javascript是单线程,是为了防止对对dom进行误操作。但浏览器是多线程的,它包含:GUI线程、js引擎线程、定时触发器线程、事件触发线程、异步http请求线程。为了确保UI渲染准确,UI渲染线程与JavaScript引擎为互斥的关系。

    javascript分位同步任务和异步任务,异步模式 不会去等待这个任务的结束才开始下一个任务,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。

异步模式对于JavaScript语言非常重要,没有它就无法同时处理大量的耗时任务。对于开发者而言。单线程下面的异步最大的难点就是代码执行的顺序混乱。

事件循环

1.javascript遇到同步任务时将其放进执行栈(先进后出)
2.遇到异步任务交给浏览器其他线程处理
3.异步任务完成后,其他线程向任务队列里推入异步回调事件
4.执行栈里所有任务执行完后,任务队列里的回调事件依序进入执行栈开始执行
5.重复上面4步

异步任务又分为宏任务和微任务,每一次执行栈清空后,先将微任务队列的回调事件推入任务栈(微任务重新产生的微任务也在这次循环中执行),等任务栈清空后再推入一个宏任务。
微任务:Promise MutationObserver queueMicroTask
宏任务:setTimeout setInterval script

image.png

手写Promise

//Promise是一个类
//Promise有状态,状态有三种类型 pending, fulfilled, rejected
//promise有两个基础方法 resolve 和 reject
//resolve和reject都是会改变Promise状态
//resolve会从pending->fulfilled,reject会从pending->rejected,状态一旦改变就不能再次改变
//Promise的构造函数接收一个执行器,该执行器接收两个参数resolve、reject,实际执行的Promise类的方法
//Promise有一个then方法,接受两个回调,一个成功回调,一个失败回调,根据状态判断执行哪个回调,回调参数是可选的(也就是可传可不传)
//resolve和reject会把接受的参数传到then回调里,所以这里需要在类里声明一个value和一个reason去保存参数
//then返回的是一个Promise(链式调用最关键的点),而且要延迟执行,这个Promise的状态就是在这次then执行里面改变的
//兼容Promise里面是延迟执行的case,把成功和失败函数缓存起来,因为一个promise可以有多个then所以用数组缓存
//当执行resolve和reject的时候要清空缓存回调函数数组,执行resolve或reject的时候使用shift方法清空数组
//then返回的Promise对象状态是由回调函数结果决定,如果回调函数返回一个普通对象,then则返回一个resolve状态,值为返回对象的Promise对象,如果返回一个Promise对象则将这个Promise对象的状态和值传递给then返回的Promise,且这个Promise对象不可以是自己
//为了实现上一步所以需要写一个方法resolvePromise做处理
//执行器步骤都需要错误处理
//then到这一步就结束了

//Promise.all()的实现,因为能通过类直接调用说明是静态方法,需要用static关键字声明
//Promise.all()接收一个数组(可以是普通对象也可以是Promise对象)
//当所有参数数组里所有Promise对象执行完成后返回一个fullfilled状态的Promise,值为Promise返回的值的数组,只要参数数组里有一个抛出一场则返回一个rejected的Promise对象
//在Promise.all里面会把参数数组里的Promise对象和普通对象分开处理,如果可以用Promise.resolve另说
//Promise.all的核心思想就是返回一个Promise对象,在该对象的执行器中遍历参数数组并执行,把结果放入到结果数组里,同时需要一个计数器判断是否完成

//race方法差不多

//Promise.resolve如果参数是Promise对象就返回参数,如果不是Promise对象就返回一个,也是个静态方法

//finally方法,接受一个函数参数,无论Promise对象状态如何都会执行这个函数参数,且返回当前Promise值,这个时候就可以借助then方法,而且当这个回调函数返回一个Promise时要等这个返回的Promise执行完才继续执行后续的then

//catch方法,接受一个函数参数返回一个Promise值,该Promise回调返回值决定,也借助then方法


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

//Promise是一个类
class LrbPromise {
    status = PENDING;  //Promise有状态,状态有三种类型 pending, fulfilled, rejected
    //resolve和reject会把接受的参数传到then回调里,所以这里需要在类里声明一个value和一个reason去保存参数
    value = void 0;
    reason = void 0;
    successCallbackList = [];
    failCallbackList = [];
    constructor(executor) { //Promise的构造函数接收一个执行器,该执行器接收两个参数resolve、reject,实际执行的Promise类的方法
        try { //执行器步骤都需要错误处理
            executor(this.resolve, this.reject);
        } catch (e) {
            this.reject(e);
        }
    }
    resolve = (value) => {  //注意这里用尖头函数保留this
        if (this.status !== PENDING) return; //状态一旦改变就不能再次改变
        this.status = FULFILLED; //resolve会改变Promise状态,从pending->fulfilled
        this.value = value; //resolve和reject会把接受的参数传到then回调里,所以这里需要在类里声明一个value和一个reason去保存参数
        while(this.successCallbackList.length) { //当执行resolve和reject的时候要清空缓存回调函数数组,执行resolve或reject的时候使用shift方法清空数组
            this.successCallbackList.shift()()
        }
    }
    reject = (reason) => { //注意这里用尖头函数保留this
        if (this.status !== PENDING) return; //状态一旦改变就不能再次改变
        this.status = REJECTED; //reject会改变Promise状态,从pending->rejected
        this.reason = reason; //resolve和reject会把接受的参数传到then回调里,所以这里需要在类里声明一个value和一个reason去保存参数
        while(this.failCallbackList.length) { //当执行resolve和reject的时候要清空缓存回调函数数组,执行resolve或reject的时候使用shift方法清空数组
            this.failCallbackList.shift()()
        }
    }
    //Promise有一个then方法,接受两个回调,一个成功回调,一个失败回调,根据状态判断执行哪个回调,回调参数是可选的(也就是可传可不传)
    then (successCallback, failCallback) {
        successCallback = successCallback ? successCallback : value => value; //回调参数是可选的(也就是可传可不传)
        failCallback = failCallback ? failCallback : e => { throw(e) }
        const promise2 = new LrbPromise((resolve, reject) => { // 注意这里用尖头函数保留this,then返回的Promise对象状态是由回调函数结果决定
            if (this.status === FULFILLED) { //如果是fulfilled状态执行成功回调
                setTimeout(() => { //注意这里用尖头函数保留this
                    try { //执行器步骤都需要错误处理
                        let x = successCallback(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                });
            } else if (this.status === REJECTED) { //如果是rejected状态执行成功回调
                setTimeout(() => {
                    try { //执行器步骤都需要错误处理
                        let x = failCallback(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                });
            } else {
                //兼容Promise里面是延迟执行的case,把成功和失败函数缓存起来,因为一个promise可以有多个then所以用数组缓存
                this.successCallbackList.push(() => {
                    setTimeout(() => { //注意这里用尖头函数保留this
                        try { //执行器步骤都需要错误处理
                            let x = successCallback(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    }, 0)
                });
                this.failCallbackList.push(() => {
                    setTimeout(() => { //注意这里用尖头函数保留this
                        try { //执行器步骤都需要错误处理
                            let x = failCallback(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    }, 0)
                });
                // this.failCallbackList.push( () => {
                //     setTimeout(() => {
                //         try {
                //             let x = failCallback(this.reason);
                //             resolvePromise (promise2, x, resolve, reject);
                //         } catch (e) {
                //             reject(e)
                //         }
                //     }, 0)
                // });
            }
        })
        return promise2;
    }
    //Promise.resolve如果参数是Promise对象就返回参数,如果不是Promise对象就返回一个,也是个静态方法
    static resolve (value) {
        return value instanceof LrbPromise ? value : new LrbPromise(resolve => resolve(x));
    }
    //Promise.all()的实现,因为能通过类直接调用说明是静态方法,需要用static关键字声明,接受一个参数数组
    //Promise.all的核心思想就是返回一个Promise对象,在该对象的执行器中遍历参数数组并执行,把结果放入到结果数组里,同时需要一个计数器判断是否完成
    static all (arr) {
        return new LrbPromise((resolve, reject) => {
            if (!Array.isArray(arr))  return reject(new TypeError('请传入数组'));
            const result = [];
            let counter = 0;
            //当所有参数数组里所有Promise对象执行完成后返回一个fullfilled状态的Promise,值为Promise返回的值的数组,只要参数数组里有一个抛出一场则返回一个rejected的Promise对象
            function addValue (i, val) {
                result[i] = val;
                counter++;
                if (counter === arr.length) {
                    resolve(result)
                }
            }
            for (let i = 0; i < arr.length; i++) {
                //在Promise.all里面会把参数数组里的Promise对象和普通对象分开处理,如果可以用Promise.resolve另说
                if (arr[i] instanceof LrbPromise) {
                    arr[i].then(res => {
                        addValue(i, res);
                    }, reject)
                } else {
                    addValue(i, arr[i]);
                }
            }
            //如果存在Promise.resolve
            // for (let i = 0; i < arr.length; i++) {
            //     LrbPromise.resolve(arr[i]).then(res => {
            //         result[i] = res;
            //         counter++;
            //         if (counter === arr.length) {
            //             resolve(result)
            //         }
            //     }, reject)
            // }
        })
    }
    //finally方法,接受一个函数参数,无论Promise对象状态如何都会执行这个函数参数,且返回当前Promise值,这个时候就可以借助then方法,而且当这个回调函数返回一个Promise时要等这个返回的Promise执行完才继续执行后续的then
    finally (callback) {
        return this.then((value) => {
            return LrbPromise.resolve(callback()).then(() => value)
        }, (reason) => {
            return LrbPromise.resolve(callback()).then(() => { throw reason })
        })
    }
    //catch方法,接受一个函数参数返回一个Promise值,该Promise回调返回值决定,也借助then方法
    catch (callback) {
        return this.then(void 0, callback)
    }
}

//then返回的Promise对象状态是由回调函数结果决定,如果回调函数返回一个普通对象,then则返回一个resolve状态,值为返回对象的Promise对象,如果返回一个Promise对象则将这个Promise对象的状态和值传递给then返回的Promise,且这个Promise对象不可以是自己
function resolvePromise (promise, x, resolve, reject) {
    if (promise === x) {
        reject(new TypeError('不可以返回自己')); //这里可以试试直接throw
    }
    if (x instanceof LrbPromise) {
        x.then(resolve, reject);
    } else {
        resolve(x);
    }
}

function getPromise () {
    return new LrbPromise((resolve, reject) => {
        // resolve(1)
        // setTimeout(() => {
        //     resolve('成功')
        // });
            // resolve('成功')
        reject('失败')
    })
}

const p2 = getPromise();

p2.then()
    .then(res => console.log('成功:', res), reason => console.log('失败:', reason))
// const p1 = new LrbPromise((resolve, reject) => {
//     // resolve(1)
//     // reject(1)
// }).then((value) => {
//     console.log(value);
// }, (reason) => {
//     console.log(reason);
// })