# 概述

首先问大家个问题:

目前在前端开发中需要处理一些异步操作的时候,使用Promise的时候你是否觉得是一种理所当然的习惯性操作呢? 我们来看下 Promise 出现的时间列表: ES2015 规范新增 Promise ES2018 规范新增 Promise.finally ES2020 规范新增 Promise.allSettled ES2021 规范新增 Promise.any 也就是说在此之前是没有Promise这个东东的,那么没有Promise的时候我们又是怎么处理异步呢? 方法一:callback 大法 弊端: 1、回调地狱 2、调试链长可读性差

    function getDataByCallback(onSuccess, onError) {

        setTimeout(() => {

            onSuccess && onSuccess(11);

        }, 1000);

        setTimeout(() => {

            onError && onError(22);

        }, 2000);

    }


    getDataByCallback(function onSuccess(result) {

        ~/~/ TODO

        console.log('onSuccess: ', result);

    }, function onError(error) {

        ~/~/ TODO

        console.log('onError: ', error);

    });

方法二:eventbus 大法

弊端:

1、代码跟踪调试麻烦;

2、效率低

    //简易的发布订阅实现

    var eventbus = {

        events: {},

        on(key, handler) {

            if (typeof key !== 'string' || typeof handler !== 'function') {

                throw Error('入参错误');

            }

            this.events[key] = this.events[key] || [];

            this.events[key].push(handler);

        },

        trigger(key, params) {

            var handlerList = this.events[key];

            if (handlerList) {

                for (let i = 0, len = handlerList.length; i < len; i++) {

                    handlerList[i](params);

                }

            }

        }

    }


    var HANDLE_KEYS = {

        GET_DATA: 'getData'

    };


    eventbus.on(HANDLE_KEYS.GET_DATA, function(result) {

        console.log('结果:', result);

    });


    console.log('开始:');

    setTimeout(() => {

        eventbus.trigger(HANDLE_KEYS.GET_DATA, 123);

    }, 2000);

方法三:事件触发类 依托交互、循环检查等等

弊端:

1、业务场景限制

2、效率稳定性不一

因此针对以上问题,以及现阶段前端的快速的发展进程,Promise的出现就很好理解了,当然也不仅仅只是Promise。只是本次我们主要讲Promise的基础和业务中总结的一些使用技巧

# 基础

Promise 英文意思为承诺,大家都知道。那么针对承诺,我们直观的关注点应该就是:

1、为什么事做的承诺

2、当前的承诺状态

3、什么时候兑现承诺

4、兑现承诺做什么,成功了怎么做?失败了怎么做?

针对这种事物的基本逻辑,Promise也将其做了相应的实现,这些大家可能也是基本都了解。

以[[Promise A+>>https://promisesaplus.com/#]]规范为例:

1、为什么事做的承诺(业务主体) executor

2、状态 Pending(等待态)、Fulfilled(成功态)、Rejected(拒绝态)

3、什么时候兑现承诺(执行回调), executor~-~-> resolve, reject

4、兑现承诺做什么(收集回调).then(onResolve, onReject) .catch(onReject) .finally(all)

# 知识点解析

1、executor 为同步执行,因此在业务开发中使用时,应该对具体的异步方法,是进行直接调用还是提供一个handler,这个需要优先考虑,如下两个场景

    const promiseFactory = value => {

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

            console.log('开始执行 ', item);

            setTimeout(() => {

                resolve(item);

            }, Math.random() * 2000);

        }).then(res => {

            console.log('执行结束', res);

            return res;

        });

    }

    // 这里其实在构建promiseList的时候,每一个executor就已经在执行了

    const executAll = (list) => {

        Promise.all(list);

    }

    const valueList = [1, 2, 3];

    const promiseList = valueList.map(promiseFactory);

    executAll(promiseList);


    // 因此可以将每个Promise异步业务进行handler化封装,后续调用时才执行

    const executAll = (list) => {

        Promise.all(list.map(func => func(~)~)~);

    }

    const valueList = [1, 2, 3];

    const promiseList = valueList.map(value => () => promiseFactory(value));

    executAll(promiseList);


    OR


    const executAll = (list) => {

        const promiseList = list.map(promiseFactory);

        Promise.all(list.map(func => func(~)~)~);

    }

    const valueList = [1, 2, 3];

    executAll(valueList);

# 2、.then(), .catch(), .finally()用法以及注意项

Promise的then(onFulfilled, onRejected)方法主要收集Fulfilled和rejected状态下具体操作 注意事项: 1、promise.then()在调用是才会执行task.Enqueue方法将其放入microtask中 2、onRejected方法只能监听,reject触发和executor方法中抛出的错误,无法监听同级onFulfilled中出现的错误,但是可以监听上级的onFulffiled的错误,如:

    new Promise((resolve, reject) => {

        resolve(11)

       // reject(111)

    }).then(res => {

        console.log('then', res)

        dd

    }, err => {

        console.log('catch',err)

    }).then(() => {


    }, err => {

        debugger

    });

3、返回值问题,分为两种:

(1)、返回值非promise对象,那么会自动转换为Fulfilled状态,并且触发后续注册的onFulfiled回调

(2)、返回值为promise,会触发PromiseResolveThenableJob,分为两步生成一个job microtask,然后转接一个microtask到后面类似于then(onFulfilled)返回一个非promise的值类型

    Promise.resolve()

        .then(() => {

            console.log('promise0')

            return Promise.resolve();

        }).then(() => {

            console.log('sdfs')

        })


    Promise.resolve()

    .then(() => { console.log('promise1') })

    .then(() => { console.log('promise2') })

    .then(() => { console.log('promise3') })

    .then(() => { console.log('promise4') })

    .then(() => { console.log('promise5') })

Promise的catch(onCatch),onCatch方法会捕获executor中的和之前的then,catch中的所有错误

Promise的finally(onCompleted),onCompleted回到Fulfilled和Rejected状态都会执行且不会接收数据

3、resolve, reject ,Promise.resolve Promise.reject用法以及注意项

resolve, reject为new Promise(executor)中executor的两个方法参数,类似事件的trigger 主要动作为修改状态、执行相应的回调。特殊情况为resolve参数为promise的情况会触发PromiseResolveThenableJob处理模式,会导致后续then收集的回调延后连个microtask执行,应为会生成一个job 和 同步结果的microtask。具体例子如后面的Promise.resolve讲解

Promise.resolve() 直接返回一个Fulfilled状态的promise实例,其中可接受4种类型参数:

1、参数为promise实例

    const promise1 = new Promise((resolve, reject) => {

        resolve(1);

    });

    const promise2 = Promise.resolve(promise1);

    console.log(promise1 === promise2); ~/~/ true

2、参数为thenable,带有then方法额对象,Promise.resolve() 方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then()方法

3、参数为一个原始值或者不带参数,那么Promise.resolve()返回一个新的状态为Fulfilledd的promise对象 针对resolve参数传递Promise对象,从而从而走A+规范PromiseResolveThenableJob的问题。特殊例子如下:

    new Promise(resolve => {
        let resolvedPromise = Promise.resolve()
        resolve(resolvedPromise)
    }).then(() => {
        console.log('resolvePromise resolved')
    })
    Promise.resolve()
    .then(() => { console.log('promise1') })
    .then(() => { console.log('promise2') })
    .then(() => { console.log('promise3') })
   //但是平时业务使用中,我们不太会去关注两个无关联性的promise的回调执行顺序。
   // Promise.reject方法执行得到一个Rejected状态的Promise对象
    const rejectedPromise = Promise.reject();

# 业务场景中的特殊用例

# 1、业务权限转接

场景: 1、转接、封装其他的异步操作,最常见的需求 2、替换、缓存、传递resolve,reject操作方法,从而达到控制异步操作响应、转移操作能力的目的

    const checkOptions = (options = {}) => {

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

            setTimeout(() => {

                if (options.ok) resolve(options.ok);

                else reject('错误');

            }, 1000);

        });

    }


    const getData = (options) => {

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

            setTimeout(() => {

                resolve('成功')

            }, 1000);

        })

    }


    const getDataHandler = (options = {}) => {

        return checkOptions(options).then(getData)

    }


    getDataHandler({ ok: false }).then(res => {

        console.log(res)

    }).catch(err => {

        console.log('error', err);

    });

# 2、promise列表,队列执行

场景:针对需要顺序执行的业务动作,其中包括promis function。如:有关联先后循序的连续型异步业务操作

    /*

    ~* promise批量串行处理

    *

    ~* @param {array} [arr=[]]

    ~* @param {function} onItemFinish 单项回调函数

    ~* @param {boolean} [isFinishWhenError=false] 为false不会因为单项错误中断串行执行,单项的rejected不抛出,直接放入fulfilled中捕获

    ~* @returns {promise}

    */

    const promiseQueue = (arr = [], onItemFinish, isFinishWhenError = false) => {

        const list = [].concat(arr);

        const isType = hand => Object.prototype.toString.call(hand);

        if(isType(list) !== '[object Array]') throw new TypeError('arr must is a array');


        const isFunction = (func) => isType(func) === '[object Function]';

        const isPromise = (func) => isType(func) === '[object Promise]';

        return list.reduce((nextPromise, currentPromise, index) => {

            return nextPromise.then(result => {

                if (!isPromise(currentPromise) && !isFunction(currentPromise)) throw new TypeError('item must is a function or a promiseFunction');if (!isPromise(currentPromise) && !isFunction(currentPromise)) throw new TypeError('item must is a function or a promiseFunction');


                if(isFunction(currentPromise)) {

                    currentPromise = currentPromise();

                }


                if (isPromise(currentPromise)) {

                    return currentPromise.then((res) => {

                        result.push(res);

                        return result;

                    }).catch(err => {

                        result.push(err);

                        return isFinishWhenError ? Promise.reject(result) : result;

                    });

                } else {

                    result.push(currentPromise);

                    return Promise.resolve(result);

                }

            }).then(function(result) {

                onItemFinish && onItemFinish(result.slice(-1)[0], index);

                return result;

            }).catch(function(result) {

                onItemFinish && onItemFinish(result.slice(-1)[0], index);

                return Promise.reject(result);

            });

        }, Promise.resolve([]));

    };


    let promises = [1, 2, 3, 4, 5, 6].map((item, index) => {

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

            console.log('发送请求', item)

            setTimeout(() => {

                ~/~/ if(item !== 3) resolve(item)

                ~/~/ else reject('222');

                resolve(item)

            }, Math.random() * 100);

        });

    });


    const itemCallback = (res) => {

        console.log('请求响应', res);

    }

    promiseQueue(promises, itemCallback, false).then(res => {

        console.log('==', res);

    });

# 3、前置请求,缓存后续队列

1、前置请求场景,如:站点配置、权限校验等等,需要等待某个异步操作完成以后才能执行后续请求,但是又不能限制业务,要做到业务无感知,可并发使用的情况

2、单个业务场景,如:上传文档操作,需要先项服务端申请token然后才能执行上传、转发等待多个并行异步操作

    /*

    * @Author: daipeng7

    * @Date: 2021-12-29 10:26:48

    * @LastEditTime: 2021-12-29 14:13:39

    * @LastEditors: daipeng7

    * @Description: Promise 缓存使用场景demo代码

    * 1、前置请求场景,如:站点配置、权限校验等等,需要等待某个异步操作完成以后才能执行后续请求,但是又不能限制业务,要做到业务无感知,可并发使用的情况

    * 2、单个业务场景,如:上传文档操作,需要先项服务端申请token然后才能执行上传、转发等待多个并行异步操作

    */

    const isType = hand => Object.prototype.toString.call(hand);

    const isFunction = (func) => isType(func) === '[object Function]';

    const isPromise = (func) => isType(func) === '[object Promise]';


    class CacheRequest {

        constructor() {

            this.cacheList  = [];
            this.cache = false;
        }
        invok(func) {
            // 缓存
            if (this.cache) return this._setCache(func);
            // 非缓存
            if (!isFunction(func) && !isPromise(func)) return Promise.resolve(func);
            if (isFunction(func)) func = func();
            if (isPromise(func)) return func;
            else return Promise.resolve(func);
        }
        // 设置缓存,提供了一个对外等待的wrap Promise对象,并且将resolve和reject闭包的方式存入了缓存,等待缓存被执行的时候再来响应调用方
        _setCache(func) {
            console.log('==收集缓存==');
            let handler;
            // 缓存执行handler,最好不要直接缓存func
            if (isFunction(func)) handler = func;
            else if (isPromise(func)) handler = () => handler;
            else handler = () => Promise.resolve(handler);
           const wrapPromise = new Promise((resolve, reject) => {
                this.cacheList.push(() => {
                   return handler().then(resolve).catch(reject);
                });
            });
            return wrapPromise;
        }
        // 执行cache
        invokCache() {
            if (this.cacheList.length && !this.cache) {
                console.log('开始执行缓存');
                const invokList = [ ...this.cacheList ];
                this.cacheList = [];
               invokList.forEach(this.invok.bind(this));
            }
        }
    }
    const request = new CacheRequest();
    // 对外调用方法
    const requestHandler = (fun, prev = false) => {

        const _promise = request.invok(fun).then(res => {

            request.cache = false;

            request.invokCache();

            return res;
        });

        if (prev) request.cache = prev;
        return _promise;
    }
    // 测试数据
    const configRequest = () => {

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

            console.log('执行config');

            setTimeout(() => {

                resolve('config');

            }, 1000);

        }).then(res => {

            console.log('响应config', res);

            return res;

        });

    }
    const asyncList = [1,2,3,4].map(item => {

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

            console.log('执行后续异步操作', item);

            setTimeout(() => {

                resolve(item);

            }, Math.random() * 2000);

        }).then(res => {

            console.log('响应后续异步操作', res);

            return res;

        });

    });
    // 并发调用

    requestHandler(configRequest, true);

    asyncList.forEach(requestHandler);

# 总结

Promise使用中的关注点是针对业务封装、转接、执行控制。这三点灵活的组合可以很好的解决业务中可能需要交互、定时器等辅助实现带来的不稳定问题,同时对于代码的可读性也会变得更高。

创建人:yinyanting