Promise原理解读-阿里云开发者社区

开发者社区> 云栖大讲堂> 正文

Promise原理解读

简介: 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
本文来源:掘金如需转载请联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
7764 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
9431 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
5460 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
3830 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
16840 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
3229 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
1132 0
+关注
云栖大讲堂
擅长前端领域,欢迎各位热爱前端的朋友加入我们( 钉钉群号:23351485)关注【前端那些事儿】云栖号,更多好文持续更新中!
3892
文章
1754
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载