跳转至

手写Promise A+规范的Promise

then方法

  • 一个promise必须提供一个then方法,用来获取当前异步操作的value或error
  • 一个promise的then方法接受两个参数:promise.then(onFulfilled,onRejected)
  • then方法返回的也是一个新的promise实例。因此可以采用链式写法,即then方法后再调用另一个then方法

手写Promise

首先需要做什么?

第一步需要创建一个container(容器,可通过其构建一个容器实例),容器的大致作用如下:

  1. 容器实例化的对象con中首先需要一个用于存储回调函数的数组
  2. con中需要有一些必要的方法,如add(向数组中添加回调函数),startup(执行数组中的回调函数)等等
  3. 容器初始化时,需要根据状态创建不同模式的容器,如:stopOnFalse(如果某一个回调函数返回false时,是否终止执行)、memory(在添加新的回调函数时,原本的回调函数是否会被再次执行)、once(数组中回调函数是否只执行一次)等等标签

container的创建

container创建demo如下:

var cache = {};// 缓存对象
var container =function (flags){
    flags = typeof flags =='string' ? (cache[flags] || createFlags(flags)): extend({},flags)
    console.log(flags)
    var carryOut,len,i,memory,stackLen, stackPoint;
    var stack = []; // 用于存储回调列表
    // 执行传递过来的回调函数数组
    var fire = function (data) {
        memory = flags.memory && data
        len = stack.length
        i = stackPoint || 0
        stackPoint = 0
        carryOut = true
        for (;i<len;i++) {
            if(stack[i].apply(data[0], data[1]) == false && flags.stopOnFalse){ 
                //执行当前函数并且为其绑定执行上下文
                break;
            }
        }
    }
    var self ={
        // add函数的写法是一个立即执行函数IIFE,后面的(arguments)则是立即调用这个匿名函数,并将                  arguments对象作为参数传递给它,其实arguments就是对应入参中的args
        // (function(args) {
        //     你可以在这里使用args访问传入的参数
        // })(arguments);
        add: function (){
            (function add(args){
                stackLen = stack.length
                // Array.from(agrs)将传进来的数据转换为真正的数组进行处理
                Array.from(args).forEach(function (arg){
                    // agr.toString()和toString.call(agr)在某种情况下是一样的效果,但是还是有一定                        的不一样
                    // 1,首先agr.toString()需要保证agr本身有toString()这个方法,否则会有未知的错误
                    // 2,toString.call(agr)则更加的安全,即使 agr 是 null 或 undefined,                              Object.prototype.toString.call(null) 和                                                    Object.prototype.toString.call(undefined) 也不会抛出错误,而是返回 "                            [object Null]" 和 "[object Undefined]"。
                    // if(agr.toString() ==='[Object Function]') {
                    // 成员是一个非数组,是一个函数的情况
                    if(toString.call(arg) ==='[object Function]') {
                        // 检测是否有重复的回调
                        if (!self.has(arg)){
                            stack.push(arg)
                        }
                        // 传进来的入参是数组的情况,就递归添加
                    } else if ( arg && arg.length && typeof arg !== 'string' ) {
                        add(arg)
                    }
                })
            })(arguments)// arguments是父级函数的入参
            if (memory) {
                stackPoint =stackLen
                fire(memory)
            }
        }, // 向回调列表中添加回调函数
        // context就是当前函数的执行上下文
        startupWith:function (context, args){
            args || [];
            args = [context,args]
            // 因为每次执行startup都是调用startupWith函数,且startupWith中主要是使用fire方法调用回调函数数组中的函数,所以只需控制是否执行fire函数即可
            if (!flags.once || !carryOut) {
                fire(args)
            }
        },
        startup: function (){
            self.startupWith(this, arguments) 
            // this就是self,arguments就是调用startup函数的入参
        }, // 开始执行回调列表中的回调函数
        has: function (fn){
            return stack.indexOf(fn) > -1; // 大于-1说明有重复的值
        }
    };
    return self;
}
function createFlags(flags){
    var res = {};
    // 用于在空白处将flags进行拆分,将flags根据空格进行拆分拆分为数组
    (flags.match(/\S+/g)||[]).forEach(function (flag) {
        res[flag] = true;
    });
    return res
}
function extend(to,from){
    for (let key in from){
        to[key] = from[key];
    }
    return to
}

Promise的创建

function Promise (func){
    var tuples = [
        // 状态 添加回调的方法(语法糖) 创建容器->回调列表  异步操作的最终的状态
        ['resolve','done',container('once memory'),'resolved'],
        ['reject','fail',container('stopOnFalse'),'rejected']
    ]
    var state = 'pending' // 只要创建promise对象就是处于pending的状态
    var chain = {
        then: function (/* fnSuccess,fnFail*/) {
            var args = arguments
            return Promise(function (resolve, reject) {
                tuples.forEach(function (tuple,index){
                    // 判断传递过来的resolve和reject是不是函数
                    var fn = toString.call(args[index]) === '[object Function]' && args[index]
                    chain[tuple[1]](function () {
                        var returned = fn && fn.apply(this, arguments)
                        if (returned && toString.call(returned.then)=='[object Function]') {
                            returned.done(resolve)
                            returned.fail(reject)
                        }
                    })
                })
            })
        }
    }
    var protect = {}
    tuples.forEach(function (tuple,index){
        // list就是创建出来的容器
        var list =  tuple[2];
        var statusString = tuple[3]
        if(statusString) {
            list.add(function () {
                state = statusString
            })
        }
        // chain [done / fail],通过chain.done  chain.fail来向对应的对象中添加回调函数的
        chain[tuple[1]] = list.add

        // 状态绑定 resolve('xxxx') reject('xxxxx')
        protect[tuple[0]] = function () {
            protect[tuple[0]+'With'](this, arguments)
        }

        // resolveWith rejectWith
        protect[tuple[0]+'With'] = list.startupWith

    })
    if(func){
        func.call(chain, protect['resolve'], protect['reject'])
    }
    return chain
}