zoukankan      html  css  js  c++  java
  • 分步理解 Promise 的实现

    一个 Promise 的运用:

    var firstPromise = new Promise(function(resolve,reject){
        setTimeout(function(){
            var result = Math.random() <= 0.5 ? 1:0;
            if(result){
                resolve('resolved');
            }else{
                reject('rejected')
            }
        },1000)
    })
    
    var secondPromise = new Promise(function(resolve,reject){
        setTimeout(function(){
            var result = Math.random() <= 0.5 ? 1:0;
            if(result){
                resolve('resolved');
            }else{
                reject('rejected')
            }
        },2000)
    })
    
    firstPromise.then(function(value){ 
        console.log(value);
        return secondPromise;
    },function(reason){
        console.log(reason);
        return secondPromise;
    }).then(function(value){ 
        console.log(value);
    },function(reason){
        console.log(reason);
    })
    
    //  1s后随机输出结果 resolved 或者 rejected
    //  再1s后随机输出结果 resolved 或者 rejected

    效果如上,在一个 promise 被完成/被拒绝时执行对应的回调取到异步结果。

    同时,以上代码使用 promise 避免了回调地狱,规范了回调操作。

    接下来,把 promise 拆成几块,学习一下怎么样的实现过程。

    步骤一、Promise 构造函数

    创建 promise 对象的构造函数,是创造 promise 的工厂。

    基础要求:Promise 函数仅产生一个对象,避免大量变量的污染,将该藏好的对象/值藏好,该暴露的暴露;Promise 接收一个函数作为参数,且该函数在构造 promise 对象时被执行;Promise 必须有个 .then 方法(后续方法可自行扩展)。

    function Promise(fn){
        this.then = function(){ };
    }

    步骤二、初始化过程,处理参数fn

    Promise 构造函数参数 fn 中传入 resolve/reject;Promise 初始化的时候执行 fn 并在 promise 得到最终结果后执行传入的 resolve/reject ;resolve/reject 函数中执行 promise 中指定的完成/拒绝时回调函数,并以最终结果作为参数。

    function Promise(fn){
    
        // 完成时
        function resolve(value) {
            console.log('value ',value);
        }
    
        // 拒绝时
        function reject(reason) {
            console.log('reason ',reason);
        }
    
        // 执行传入的fn
        function init(fn, onResolved, onRejected) {
            try {
                fn(function (value) {
                    onResolved(value);
                }, function (reason) {
                    onRejected(reason);
                })
            } catch (err) {
                onRejected(err);
            }
        }
    
        init(fn, resolve, reject);
        
        this.then = function(){ };
    }
    
    var promise = new Promise(function(resolve,reject){ 
        setTimeout(function(){
            var result = Math.random() <= 0.5 ? 1:0;
            if(result){
                resolve('resolved') 
            }else{
                reject('rejected')
            }
        },1000) 
    })
    
    // 1s后随机输出 value resolved 或者 reason rejected

    步骤三、.then 里的处理流程

    在promise中, .then 将传入的 resolvedHandle 和 rejectedHandle 函数存入 promise 的 handlers 中作为回调列表中的一项,在需要的时候(Promise被完成的时候)携带最终结果执行。

    首先,假设有个异步操作,而且已经知道回调函数是什么,代码如下:

    var resolvedHandle = function(res){ console.log(res) };
    var rejectedHandle = function(err){ console.log(err) };
    
    setTimeout(function(){
        var result = Math.random() <= 0.5 ? 1:0;
        if(result){
            resolvedHandle('resolved');
        }else{
            rejectedHandle('rejected');
        }
    },1000)
    
    // 1s后输出 resolved 或者 rejected

    而对于 promise 而言,回调函数是在 .then 中传入并且在 promise 中给定义的,并且为了实现链式的操作, .then 中必须有返回一个对象,且对象须是一个携带 .then 方法的对象或函数或为一个 promise ,才足以继续执行.then。

    // fn 作为初始化Promise时传入的函数,应该被立即执行并取出其中的调用
    function Promise(fn) {
    
        var $resolveHandle = function (res) { };
        var $rejectHandle = function (err) { };
    
        // 执行Promise被完成时函数
        function resolve(value) {
            try {
                var then = getThen(value);
                if (then) {
                    init(then.bind(value), resolve, reject);
                    return;
                };
                fulfill(value);
            } catch (err) {
                reject(err);
            }
        }
    
        // 完成时
        function fulfill(value) {
            $resolveHandle(value);
            $resolveHandle = null;
        }
    
        // 拒绝时
        function reject(reason) {
            $rejectHandle(reason);
            $rejectHandle = null;
        }
    
        // 执行传入的fn并执行回调
        function init(fn, onResolved, onRejected) {
            try {
                fn(function (value) {
                    onResolved(value);
                }, function (reason) {
                    onRejected(reason);
                })
            } catch (err) {
                onRejected(err);
            }
        }
    
        init(fn, resolve, reject);
    
        function getThen(value) {
            var t = typeof value;
            if (value && (t === 'object' || t === 'function')) {
                var then = value.then;
                if (typeof then === 'function') {
                    return then;
                }
            }
            return null;
        };
    
        this.then = function (resolveHandle, rejectHandle) {
            return new Promise(function (resolve, reject) {
                $resolveHandle = function (result) {
                    resolve(resolveHandle(result));
                }
                $rejectHandle = function (reason) {
                    if(rejectHandle){
                        resolve(rejectHandle(reason));
                    }else{
                        reject(reason)
                    }
                }
            })
        }
    }
    
    var firstPromise = new Promise(function (resolve, reject) {
        setTimeout(function () {
            var result = Math.random() <= 0.5 ? 1 : 0;
            if (result) {
                resolve('resolved');
            } else {
                reject('rejected');
            }
        }, 1000);
    })
    
    var secondPromise = new Promise(function (resolve, reject) {
        setTimeout(function () {
            var result = Math.random() <= 0.5 ? 1 : 0;
            if (result) {
                resolve('resolved 2');
            } else {
                reject('rejected 2');
            }
        }, 2000);
    })
    
    firstPromise.then(function (res) {
        console.log('res ', res);
        return secondPromise;
    }).then(function (res) {
        console.log('res 2 ', res);
    }, function (err) {
        console.log('rej 2 ', err);
    })
    // 1s后随机输出 res resolved  或者  rej rejected
    // 又1s后输出 res 2 resolved 2 或者 rej 2 rejected 2 或者 rej 2 rejected

    至此,上面的代码基本算是满足了一个 promise 的实现思路,但离正规军 promise 实现还存在一段距离

    o(╥﹏╥)o...接下去学习吧。

    步骤四、Promise/A+规范

    由于 Promise/A+规范较长,就不放到文章里了,给链接吧(中午版是自己翻译的,有出入的地方还请以英文原版为准)

    Promise/A+ 规范 [ 原文 ]

    Promise/A+ 规范 [ 译文 ]

    对照promise/A+规范,以上的Promise代码还存在问题:

      1.promise还需要存储promise状态和最终结果,以便后续被多次使用;

      2.同一个promise的.then方法中注册的回调函数可被多次执行,且回调函数可以是个列表;

      3.事件调度,回调函数应该在本轮.then方法所在事件队列结束后被调用;

      4.捕捉错误并做拒绝处理;

      更多细节...

    继续改进,最后整改后的代码大致是这样的:

    function Promise(fn) {
        /* state
        * 0 : pending 
        * 1 : resloved 
        * 2 : rejected 
        */
        var state = 0;
        var value = null;
        var handlers = [];
    
        function fulfill(result) {
            state = 1;
            value = result;
            handlers.forEach(handle);
            handlers = [];
        };
    
        function reject(error) {
            state = 2;
            value = error;
            handlers.forEach(handle);
            handlers = [];
        };
    
        function resolve(result) {
            try {
                var then = getThen(result);
                if (then) {
                    init(then.bind(result), resolve, reject);
                    return;
                }
                fulfill(result);
            } catch (err) {
                reject(err);
            }
        };
    
        function getThen(value) {
            var type = typeof value;
            if (value && (type === 'object' || type === 'function')) {
                var then = value.then;
                if (typeof then === 'function') {
                    return then;
                }
            }
            return null;
        };
    
        function handle(handler) {
            if (state === 0) {
                handlers.push(handler);
            } else {
                if (typeof handler.onResolved === 'function') {
                    if (state === 1) {
                        handler.onResolved(value);
                    };
                    if (state === 2) {
                        handler.onRejected(value);
                    };
                }
            }
        };
    
        // 放到事件队列最后,在本轮事件执行完后执行
        function timeHandle(callback, newValue) {
            setTimeout(function () {
                callback(newValue);
            }, 0)
        }
    
        function init(fn, onResolved, onRejected) {
            try {
                fn(function (value) {
                    timeHandle(onResolved, value);
                }, function (reason) {
                    timeHandle(onRejected, reason);
                });
            } catch (err) {
                timeHandle(onRejected, err);
            }
        };
    
        init(fn, resolve, reject);
    
        this.then = function (onResolved, onRejected) {
            if (!onResolved && !onRejected) {
                throw new TypeError('One of onResolved or onRejected must be a function.')
            };
            return new Promise(function (resolve, reject) {
                handle({
                    onResolved: function (result) {
                        if (typeof onResolved === 'function') {
                            try {
                                resolve(onResolved(result));
                            } catch (err) {
                                reject(err);
                            }
                        } else {
                            resolve(result);
                        }
                    },
                    onRejected: function (error) {
                        if (typeof onRejected === 'function') {
                            try {
                                resolve(onRejected(error));
                            } catch (err) {
                                reject(err);
                            }
                        } else {
                            reject(error);
                        }
                    }
                })
            })
        };
    }
    
    var firstPromise = new Promise(function (resolve, reject) {
        setTimeout(function () {
            var result = Math.random() <= 0.5 ? 1 : 0;
            if (result) {
                resolve('resolved 1');
            } else {
                reject('rejected 1');
            }
        }, 1000);
    })
    
    var secondPromise = new Promise(function (resolve, reject) {
        setTimeout(function () {
            var result = Math.random() <= 0.5 ? 1 : 0;
            if (result) {
                resolve('resolved 2');
            } else {
                reject('rejected 2');
            }
        }, 3000);
    })
    
    firstPromise.then(function (res) {
        console.log('res 1 ', res);
        return secondPromise;
    }, function (err) {
        console.log('rej 1 ', err);
        return secondPromise;
    }).then(function (res) {
        console.log('res 2 ', res);
    }, function (err) {
        console.log('rej 2 ', err);
    })
    
    /* *
     * 1s后输出 res 1 resolved 1 或者 rej 1 rejected 1
     * 2s后输出 res 2 resolved 2 或者 rej 2 rejected 2
     * */

    通过板块一、二、三的知识点,即可大致摸清promise的实现;板块四加上一些补充和限制,遵循一些规范,提高promise功能的可扩展性。

    学会了怎么理解promise,更重要的是学会正确的使用它。

    正确使用 Promise

    promise 在业务开发中多用来处理异步或者多层回调的情况。

    基础使用 Promise MDN 及相关介绍文档中的案例为准,这里不一一赘述了... 这里简单的列出两个在使用 promise 过程中比较需要注意的点:

    1. 不同平台环境下 Promise 的方法和遵循规则略微有些出入,详情以各平台环境下的 Promise 对象为基准。

      如 es6 Promise 存在Promise.race,Promise.all等方法,node中则没有这些方法。

      如 浏览器 Promise 事件调度走的是 setTimeout,node 走的是 process.nextTick 。(参考 [asap] )

    2. Promise 虽可解决回调操作的规范问题(回调地狱),但也不能滥用 Promise (可能会占用过多内存)。

    promise 解决后的结果会被存于内存中,被对应 promise 引用着,将上面的最终代码中测试的两个 promise 改成如下:

    var firstPromise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                var result = Math.random() <= 0.5 ? 1 : 0;
                var str = '';
                var i = 0, num = 500000;
                for (; i < num; i++) {
                    str += 'promise '
                }
                if (result) {
                    resolve('resolved 1 : ' + str);
                } else {
                    reject('rejected 1 : ' + str);
                }
            }, 1000);
        })

    则内存占用情况如下:

    这些是一些平台差异或业务需求方面的不同点,对 Promise 核心实现并影响甚微,对 Promise 扩展方法有影响,对业务中 Promise 的使用有影响。

    参考

    1. Promise/implementing 

    2. Promise 实现代码阅读

  • 相关阅读:
    php 下载保存文件保存到本地的两种实现方法
    MySQL select语句直接导出数据
    Go学习笔记03-附录
    Go学习笔记02-源码
    Go学习笔记01-语言
    Go语言极速入门手册
    最简单的抓取网络图片或音乐文件
    使用PHP生成PDF文档
    Oracle常用函数
    ORACLE常用数值函数、转换函数、字符串函数
  • 原文地址:https://www.cnblogs.com/ys-ys/p/9800971.html
Copyright © 2011-2022 走看看