Promise原理及手写Promise

一、Promise 原理:

Promise 原理围绕以下三个问题进行解决:

(有任何一步不了解的话请先往下看手写Promise,会一步步剖析原理,看完后再继续回顾这里!!)

1. 怎么实现异步?

Promise内部then函数注册后续需要执行的函数,resolve函数执行。需要保证函数在执行前都已注册好,

解決:resolve内部执行函数的代码需要加入延时机制setTimeout(0)放在任务队列的末尾

思考新问题:如果Promise异步操作已经成功,这时,在异步操作成功之前注册的回调都会执行,但是在Promise异步操作成功这之后调用的then注册的回调就再也不会执行了  (加入状态机制即可解决)

2. 怎么实现在Promise异步操作成功这之后调用的then注册的回调也会执行?

加入状态机制,若为pending,则将函数注册,等待后续resolve调用。若为fulfilled,则立即执行resolve函数,并将状态设为fulfilled

3. 怎么实现链式调用?

若是检测到resolve函数的参数newValue是一个promise对象(有then函数的对象),就使用对象冒充,

调用它的then函数(下一个函数newValue,和它的resolve)

二、手写Promise

1. 极简promise雏形

function Promise(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调
 
    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
    };
 
    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }
 
    fn(resolve);
}
  1. 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
  2. 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当异步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;

2. 加入延时机制

原因:需要保证函数在执行前都已注册好

实现:通过setTimeout机制,将resolve中执行回调的逻辑放置到JS任务队列末尾,以保证在resolve执行时,then方法的回调函数已经注册完成。(若不清楚为什么能放在js任务队列末尾,请自行了解js事件循环机制)

改造resolve函数

function resolve(value) {
    setTimeout(function() {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 0)
}

3. 加入状态机制

它总共有三种状态:pending(默认,等待状) fulfilled(成功状) rejected(失败状)

状态一旦改变,就不会改变

  • 在then函数中,若状态为pending,则将函数注册,等待后续resolve调用。
  • 若状态为fulfilled,则立即执行resolve函数,将状态设为fulfilled

1

function Promise(fn) {
    var state = 'pending',
        value = null,
        callbacks = [];
 
    this.then = function (onFulfilled) {
        if (state === 'pending') {
            callbacks.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };
 
    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                callback(value);
            });
        }, 0);
    }
 
    fn(resolve);
}

4. 实现链式Promise

所谓的链式Promise:

链式Promise是指在当前promise达到fulfilled状态后,即开始进行下一个promise(后邻promise)。那么我们如何衔接当前promise和后邻promise呢?(这是这里的难点)。

实现:在then方法里面return一个promise就好啦。传递then方法生成的新promise的resolve方法,resolve(后邻promise),在resolve中,检测到参数是一个promise对象(有then函数的对象),就使用对象冒充,将改变后邻promise的resolve指向当前resolve(即将resolve传到后邻promise的then,并直接返回)
 

function Promise(fn) {
    var state = 'pending',
        value = null,
        callbacks = [];
 
    this.then = function (onFulfilled) {
        return new Promise(function (resolve) {
            handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    };
 
    function handle(callback) {
        if (state === 'pending') {
            callbacks.push(callback);
            return;
        }
        //如果then中没有传递任何东西
        if(!callback.onFulfilled) {
            callback.resolve(value);
            return;
        }
 
        var ret = callback.onFulfilled(value);
        callback.resolve(ret);
    }
 
    
    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 0);
    }
 
    fn(resolve);
}

比如:来分析一个例子

getUserId()
    .then(getUserJobById)
    .then(function (job) {
        // 对job的处理
    });
 
function getUserJobById(id) {
    return new Promise(function (resolve) {
        http.get(baseUrl + id, function(job) {
            resolve(job);
        });
    });
}
  1. then方法中,创建并返回了新的Promise实例,这是串行Promise的基础,并且支持链式调用。
  2. handle方法是promise内部的方法。then方法传入的形参onFulfilled以及创建新Promise实例时传入的resolve均被push到当前promise的callbacks队列中,这是衔接当前promise和后邻promise的关键所在(这里一定要好好的分析下handle的作用)。
  3. getUserId生成的promise(简称getUserId promise)异步操作成功,执行其内部方法resolve,传入的参数正是异步操作的结果id
  4. 调用handle方法处理callbacks队列中的回调:getUserJobById方法,生成新的promise(getUserJobById promise)
  5. 执行之前由getUserId promise的then方法生成的新promise(称为bridge promise)的resolve方法,传入参数为getUserJobById promise。这种情况下,会将该resolve方法传入getUserJobById promise的then方法中,并直接返回。
  6. 在getUserJobById promise异步操作成功时,执行其callbacks中的回调:getUserId bridge promise中的resolve方法
  7. 最后执行getUserId bridge promise的后邻promise的callbacks中的回调。

5.加入错误处理

function Promise(fn) {
    var state = 'pending',
        value = null,
        callbacks = [];
 
    this.then = function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    };
 
    function handle(callback) {
        if (state === 'pending') {
            callbacks.push(callback);
            return;
        }
 
        var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
            ret;
        if (cb === null) {
            cb = state === 'fulfilled' ? callback.resolve : callback.reject;
            cb(value);
            return;
        }
        // 使用try-catch捕获错误
        try {
            ret = cb(value);
            callback.resolve(ret);
        } catch (e) {
            callback.reject(e);
        } 
    }
 
    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        execute();
    }
 
    function reject(reason) {
        state = 'rejected';
        value = reason;
        execute();
    }
 
    function execute() {
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 0);
    }
 
    fn(resolve, reject);
}

后话

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,欢迎关注小编,一起涨知识。

猜你喜欢

转载自blog.csdn.net/weixin_38131507/article/details/103306282