Promise原理解读

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: promise是干什么的 在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,然而在需要多次回调嵌套的时候,就容易进入回调地狱了,promise解决了这一问题 Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。

promise是干什么的

在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,然而在需要多次回调嵌套的时候,就容易进入回调地狱了,promise解决了这一问题

Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了语法,原生提供了Promise。所谓Promise ,简单说就是一个容器,里面保存着某个未来才回结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。 Promise对象的状态不受外界影响

promise是什么样的

  • promise对象上的方法
Promise.all()
Promise.race()
Promise.reject()
Promise.resolve()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.prototype.then()
复制代码
  • 三种状态: promise只有三种状态
pending     进行中
fulfilled   已经成功
rejected    已经失败
复制代码
  • 状态改变:

Promise对象的状态改变,只有两种可能:

pending => fulfilled
pending => rejected
复制代码

基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例

const p = new Promise(function(resolve,reject){
    if(/*异步操作成功*/){
        resolve(value);
    }else{
        reject(error);
    }
})
复制代码

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise 实例生成以后,可以用then 方法分别指定resolved状态和rejected状态的回调函数。

p.then(function(value){
        //success
    },function(error){
        //failure
});
复制代码

原理解析

针对以上的基本用法,我们深入解析一下Promise的实现原理:

首先promsie有三种状态,它是个构造函数,且接收一个函数作为参数,这个函数我们称之为excutor,函数里有两个参数resolve和reject,这两个回掉的作用我们在基本用法里已经说了

跟jQuery链式调用相似,promise的链式调用是在 Promise.prototype.then()Promise.prototype.catch()里返回了promise实例 这张图清楚的描述了promise链式调用的过程

  • 构造函数
const PENDING = 'pengding'; // 初始态
const FULFILLED = 'fulfilled';// 成功态
const REJECTED = 'rejected';// 成功态

function Promise(excutor) {
    let self = this;
    self.status = PENDING; // 设置状态
    // 定义存放成功回调的数组
    self.onResolveCallbacks = [];
    // 定义存放失败回调的数组
    self.onRejectCallbacks = [];
    // 当调用此方法的时候,如果promise状态为pending时可以以转成成功态,如果已经是成功或者失败则什么都不做
    function resolve(value) {
        if (self.status == PENDING){ // 如果是初始态则转成成功态
            self.status = FULFILLED;
            self.value = value; // 成功后会得到一个值且不能改变
            self.onResolveCallbacks.forEach(cb => cb(self.value))  // 调用所有成功的回调
        }
    }
    function reject(reason) {
        if(self.status == PENDING){  // 如果是初始态则转成失败态
            self.status = REJECTED;
            self.value = reason;
            self.onRejectCallbacks.forEach(cb=>cb(self.value))
        }
    }
    try{
        // excutor函数执行可能会异常,需要捕获,如果出错需要用这个错误对象reject
        excutor(resolve,reject)
    }catch (e) {
        // excutor执行失败需要用失败原因reject这个promise
        reject(e)
    }
}
复制代码
  • then方法 在基本用法中我们知道在调用then方法时传进去的是两个函数,分别对应的是promise中异步方法成功和失败时的回调
Promise.prototype.then = function (onFulfilled, onRejected) {
    // 如果成功和失败的回调没传,表示这个then没任何逻辑,只会把值往后抛
    onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value=>value;
    onRejected = typeof onRejected == 'function' ? onRejected : reason=>{ throw reason};
    let self = this;
    let promise2;
    // 为了实现链式调用,每一种状态都要返回的是一个promise实例
    if(self.status == FULFILLED){ //如果promise状态已经是成功态了,onFulfilled直接取值
        return promise2 = new Promise(function (resolve,reject) {
            setTimeout(function () { // 保证返回的promise是异步
                try{
                    onFulfilled(self.value);
                }catch (e) {
                    // 如果执行成功的回调过程中出错,用错误原因把promise2 reject
                    reject(e)
                }
            })
        });
    }
    if(self.status == REJECTED){
        return promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try{
                    onRejected(self.value);
                }catch(e){
                    reject(e)
                }
            })
        })
    }
    if(self.status == PENDING){
        return promise2 = new Promise(function (resolve, reject){
            // pending状态时把所有的回调函数都添加到实例的两个堆栈中暂存,等状态改变后依次执行,其实这个过程就是观察者模式
            self.onResolveCallbacks.push(function () {
                setTimeout(function () {
                    try{
                        onFulfilled(self.value);
                    }catch(e){
                        reject(e)
                    }
                })
            });
            self.onRejectCallbacks.push(function () {
                setTimeout(function () {
                    try{
                         onRejected(self.value);
                    }catch(e){
                        reject(e)
                    }
                })
            })
        });
    }
};
复制代码

以上只是支持了一次then的调用,而现实中我们会有这种需求

const p = new Promise(function(resolve,reject){
    if(/*异步操作成功*/){
        resolve(value);
    }else{
        reject(error);
    }
});
p.then(function(){
    //success
}).then(function(){
    //success
}).then(function(){
    //success
}).catch(function(e){
    //failure
})
复制代码

这种连续链式调用then方法,连续返回promise实例的情况,而且我们要兼容then方法里返回的不是promise对象,这要求对then优化,加入一个解析promise的方法resolvePromise。这样我们就会遇到三种情况:

  • 返回值是promise实例

  • 返回值是个thenable对象或者函数

  • 没有返回值或者只返回一个普通值

    先看一下什么是thenable对象

const thenable = {
    // 所谓 thenable 对象,就是具有 then 属性,而且属性值是如下格式函数的对象
    then: (resolve, reject) => {
        resolve(200)
    }
}
复制代码
//resolvePromise
function resolvePromise(promise2,x,resolve,reject){
    if(promise2 === x){
        return reject(new TypeError('循环引用'))
    }
    let called = false;// 是否resolve或reject被调用,这两个回调只能被调用一次
    if(x instanceof Promise){
        if(x.status === PENDING){
            x.then(function (y) { // 深度递归
                resolvePromise(promise2,y,resolve,reject)
            },reject)
        }else{
            x.then(resolve,reject)
        }
    }else if(x != null && ((typeof x =='object') || (typeof x == 'function'))){
            // x 是个thenable对象或函数
        try{
            let then = x.then;
            if(typeof then == 'function'){
                then.call(x,function (y) {
                    // 如果promise2已经成功或者失败了就不要再调用了
                    if(called) return;
                    called = true;
                    resolvePromise(promise2,y,resolve,reject)
                },function (err) {
                    if(called) return;
                    called = true;
                    reject(err)
                })
            }else{
                // 到此的话x不是个thenabe对象,直接把它当成值 resolve promise2就可以了
                resolve(x)
            }
        }catch (e) {
            if(called) return;
            called = true;
            reject(e)
        }
    }else{
        // 如果x是个普通值,则用x的值去resolve promise2
        resolve(x)
    }
}
复制代码

优化后的then方法

Promise.prototype.then = function (onFulfilled, onRejected) {
    // 如果成功和失败的回调没传,表示这个then没任何逻辑,只会把值往后抛
    onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value=>value;
    onRejected = typeof onRejected == 'function' ? onRejected : reason=>{ throw reason};
    let self = this;
    let promise2;
    if(self.status == FULFILLED){ //如果promise状态已经是成功态了,onFulfilled直接取值
        return promise2 = new Promise(function (resolve,reject) {
            setTimeout(function () {
                try{
                    let x = onFulfilled(self.value);
                    if(x instanceof Promise){
                        // 如果获取到返回值X,会走解析promise的过程
                        resolvePromise(promise2,x,resolve,reject)
                    }
                }catch (e) {
                    // 如果执行成功的回调过程中出错,用错误原因把promise2 reject
                    reject(e)
                }
            })
        });
    }
    if(self.status == REJECTED){
        return promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try{
                    let x = onRejected(self.value);
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e)
                }
            })
        })
    }
    if(self.status == PENDING){
        return promise2 = new Promise(function (resolve, reject){
            self.onResolveCallbacks.push(function () {
                setTimeout(function () {
                    try{
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                })
            });
            self.onRejectCallbacks.push(function () {
                setTimeout(function () {
                    try{
                        let x = onRejected(self.value);
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                })
            })
        });
    }
};
复制代码
  • catch方法

catch原理就是只传失败的回调

Promise.prototype.catch = function(onReject){
    this.then(null,onReject);
};
复制代码
  • 最后导出这个类就可以了
try {
    module.exports = Promise
} catch (e) {
    console.log(e)
}
复制代码

实际项目中的应用---封装一个异步加载图片的方法

function imgLoad(url) {
    return new Promise(function(resolve, reject) {
      var request = new XMLHttpRequest();
      request.open('GET', url);
      request.responseType = 'blob';
      request.onload = function() {
        if (request.status === 200) {
          resolve(request.response);
        } else {
          reject(Error('Image didn\'t load successfully; error code:' + request.statusText));
        }
      };
      request.onerror = function() {
          reject(Error('There was a network error.'));
      };
      request.send();
    });
  }

  var body = document.querySelector('body');
  var myImage = new Image();

  imgLoad('XXX.jpg').then(function(response) {
    var imageURL = window.URL.createObjectURL(response);
    myImage.src = imageURL;
    body.appendChild(myImage);
  }, function(Error) {
    console.log(Error);
  });
复制代码

参考资料

MDN Promise

Promises/A+


原文发布时间为:2018年6月11日
原文作者:toymm
本文来源: 掘金如需转载请联系原作者
相关文章
|
前端开发
Promise的用法&原理&手写实现-2
Promise的用法&原理&手写实现-2
43 1
|
6月前
|
存储 前端开发 API
技术笔记:Promise的原理探究及手写Promise
技术笔记:Promise的原理探究及手写Promise
36 0
|
7月前
|
存储 前端开发 安全
快速了解std::promise的工作原理和使用
快速了解std::promise的工作原理和使用
195 3
|
7月前
|
前端开发 JavaScript API
Promise.all() 的原理与实战:简化异步逻辑的不二选择
Promise.all() 的原理与实战:简化异步逻辑的不二选择
Promise.all() 的原理与实战:简化异步逻辑的不二选择
|
前端开发 JavaScript API
Promise的用法&原理&手写实现-1
Promise的用法&原理&手写实现-1
57 0
|
存储 缓存 自然语言处理
吊打面试官:promise原理详解
吊打面试官:promise原理详解
199 0
|
前端开发
浏览器原理 18 # Promise 到底解决了什么问题呢?
浏览器原理 18 # Promise 到底解决了什么问题呢?
123 0
浏览器原理 18 # Promise 到底解决了什么问题呢?
|
前端开发
重新手写promise,理解核心的异步链式调用原理
重新手写promise,理解核心的异步链式调用原理
177 0
|
前端开发 JavaScript
通过polyfill理解Promise原理
一个挺经典的前端面试题,自己polyfill实现Promise。在网友实现的基础上,自己理解加上注释。 这个问题涉及到了JavaScript作用域规则、事件循环、函数上下文、原型继承等诸多基础知识,理解完感觉很有收获,以注释形式记录下来。
|
前端开发 JavaScript 测试技术
Promise.race() 原理解析及使用指南
Promise 对象是 ECMAScript 6 中新增的对象,主要将 JavaScript 中的异步处理对象和处理规则进行了规范化。前面介绍了《Promise.any() 原理解析及使用指南》、《Promise.all() 原理解析及使用指南》和《Promise.allSettled() 原理解析及使用指南》
399 0
Promise.race() 原理解析及使用指南