在 JavaScript Promise 快速入门 这一篇对Promise 及其基本用法做了简单的介绍。本篇介绍Promise 的完整功能以及对Promise 的来源以及实现进行解密。

Promise 是什么?

该怎么解释Promise 呢? 试着从以下几种角度来理解:

  • 从代码的角度: Promise 是ECMAScript 6中提供了一个类 ,Promise 对象可以让异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数的方式。
  • 从Promise工作机制的角度:Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是then方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。

如果到这里还不理解Promise 是什么?那就看看Promise 是怎么来的。

Promise 是怎么来的?

JavaScript是单线程语言,也就是没有像Java语言一样有Thread这种线程类(可以单独的启动一个线程运行任务)。 换言之, JavaScript是一种同步的编程语言, 但是往往JavaScript也有很多异步编程的需要,最典型的就是AJAX,如何实现AJAX呼叫后端服务之后的回调呢? 这里为了让演示更简单和直观,使用setTimeout延迟调用代替AJAX。

传统的回调方式

传统的回调将回调函数作为参数传递给异步函数,在异步函数执行完成之后,调用回调函数。以以下代码为例:

var successCallBack = function(result){console.log("成功执行的结果是="+result)
}
var failCallBack = function(result){console.log("失败执行的结果是="+result)
}
function asyncFunc(successCallBackFunc,failCallBackFunc){var isSuccess = true;//使用setTimeout模拟异步行为setTimeout(function(){//根据返回结果设置isSuccess的值if(isSuccess){successCallBackFunc("返回值1");}else{failCallBackFunc("返回值2");}},1000);
}

上面的这种写法对于单层的回调来说看起来也能接受,但是如果是多层回调的话,代码就很复制了,也就是经典的回调地狱。

Promise 的出现

早期的JavaScript一般也是使用上面的方式来处理回调的,是否有避免回调地狱的写法呢?
于是有一些JavaScript组织探求比较好的写法, 这其中最知名的就是CommonJS 。

CommonJS 是一个由服务的JavaScript运行库作者组成的小组,也就是CommonJS 是JavaScript的一个社区, 或者是一个民间组织,不是官方的标准。

CommonJS 提出了Promise 的方式,JavaScript也很认同这种方式,于是在JavaScript发布的新版本ECMAScript 6中,就将Promise 作为JavaScript的一个原生类了。

ECMAScript 6 是2015年发布的JavaScript 语言的标准。Promise 是ECMAScript 6 中的一个类。

Promise的基本用法

对于上面的立子, 使用Promise,aysnFunc返回一个Promise 对象,则简写为:

let promise = aysnFunc();
promise.then(successCallBackFunc,failCallBackFunc);

再简化一点:
aysnFunc().then(successCallBackFunc,failCallBackFunc);

为什么叫Promise ?

Promise ,翻译是承诺, 也就是保证某人会做某事或是某件事情会发生。结合then() 方法等,形象的理解一下,虽然我是异步执行的,但是我承诺在干完该干的事情之后,我会把承诺的事情做完。

浏览器对Promise 的支持

因为Promise 是在ECMAScript 6才开始出现的,所以对于一些早期的浏览器版本是不支持的,对于
Safari 10,Windows 的 Edge 14以上的浏览器支持。完整的支持参考下表:

ECMAScript 6的Promise

  • Promise 是ECMAScript 6的一个类
  • Promise 对象表示异步操作的最终完成(或失败)及其结果值。
  • Promise 对象具有静态方法和原型方法,静态方法可以独立应用,原型方法需要应用于Promise对象的示例。
    当创建一个Promise 时,它处于pending状态,Promise 根据是否fulfilled(已成功)或rejected(已失败),将运行以下三种方法中的一种或多种:
  • Promise.prototype.catch(onRejected)
  • Promise.prototype.then(onFulfilled, onRejected)
  • Promise.prototype.finally(onFinally)
    下图显示了 then 和 .catch 方法的流程。由于它们返回一个 Promise ,它们可以再次被链式调用。不管 promise 最后的状态,在执行完then 或 catch 指定的回调函数以后,都会执行finally方法指定的回调函数。

Promise 的状态

Promise 有3种状态:

  1. Pending, 进行中
  2. Resolved(Fullfilled),已完成
  3. Rejected,已失败
    在早期的Promise,完成的状态是Fullfilled,后来习惯就称为Resolved。

Promise 状态的改变:

  1. Pending -> Resolved
  2. Pending -> Rejected

链式调用

通过多次调用then() 可以添加多个回调函数,它们按照插入顺序执行。
Promise链可以实现在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。

在没有Promise之前,多重异步操作,会导致经典的回调地狱:

doSomething(function(result) {doSomethingElse(result, function(newResult) {doThirdThing(newResult, function(finalResult) {console.log('Got the final result: ' + finalResult);}, failureCallback);}, failureCallback);
}, failureCallback);

使用Promise , 形成一个Promise 链

doSomething().then(function(result) {return doSomethingElse(result);
})
.then(function(newResult) {return doThirdThing(newResult);
})
.then(function(finalResult) {console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

catch(failureCallback) 是 then(null, failureCallback) 的缩略形式
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。

多说一点, 上面的写法也可以使用箭头函数进一步简化:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。

Promise.resolve() 和 Promise.reject()

两者都是new promise() 的快捷方式。

  1. Promise.resolve(value)
    等同于
    new Promise(function (resolve) {resolve(value)})
  1. Promise.reject(value)
    等同于
    new Promise(function(resolve,reject){reject(new Error("xx"));});

Promise.all() 和Promise.race()

  • Promise.all() 接收一个Promise对象的数组作为参数, 当数组里的所有Promise对象全部变为resolve时, 该方法才resolve;如果其中一个Promise对象reject, 则该方法reject。
Promise.all([new Promise(function(resolve){setTimeout(function(){resolve(100);},100);}),new Promise(function(resolve){setTimeout(function(){resolve(200);},200);}),new Promise(function(resolve){setTimeout(function(){resolve(300);},300);})]).then(function(value){console.log(value);  //[100,200,300]});
  • 接收一个Promise对象的数组作为参数, 只要一个Promise对象变为Resolved或者Rejected状态,该方法返回,进行后面的处理。
Promise.race([new Promise(function(resolve){setTimeout(function(){resolve(100);},100);}),new Promise(function(resolve){setTimeout(function(){resolve(200);},200);}),new Promise(function(resolve){setTimeout(function(){resolve(300);},300);})]).then(function(value){console.log(value);  //100});

使用Promise 封装AJAX

上面一直说Promise 可以让异步的回调使用同步的方式书写,典型的就是在AJAX中使用, 那么AJAX是如何结合Promise 使用的呢? 看如下代码:

function ajaxGet(url) {// 返回一个Promise对象return new Promise(function (resolve, reject) {// 创建一个XHR对象var req = new XMLHttpRequest() || new ActiveXObject('Microsoft.XMLHTTP')req.open('GET', url, true)// 注意:使用req.onload监听req的状态req.onload = function () {if (req.readyState == 4 && req.status == 200) {resolve(req.response)} else {reject(Error(req.statusText))}}// 网络错误req.onerror = function () {reject(Error("Network Error"));};// 发送请求req.send();});
}

调用AJX的方式可以简化为:

ajaxGet("http://xxx").then(function (response) {console.log(response);
}).catch(function (error) {console.log(error);
})

注意:

  • 在AJAX中使用的事件是onreadystatechange, 这里使用的是onload。为什么不使用
    onreadystatechange呢?因为Promise 的状态改变时单向的,一次性的, 一旦改变,状态就会凝固,也即是req.onreadystatechange只会被调用一次。如果用onreadystatechange

Promise 的典型使用场景

Promise 应用在很多功能场景中, 包括:

  • 文件操作
  • API调用
  • DB调用
  • IO调用
  • Node.js 基于Promise

Promise 实现原理解密

说了这么多, 感觉Promise 很强大,也很神秘, 其实Promise 也不是那么神秘,简单的模拟一下Promise的实现:

function Promise(fn) {var state = 'pending',value = null,deferreds = [];this.then = function (onFulfilled) {if (state === 'pending') {deferreds.push(onFulfilled);return this;}onFulfilled(value);return this;};function resolve(newValue) {value = newValue;state = 'fulfilled';setTimeout(function () {deferreds.forEach(function (deferred) {deferred(value);});}, 0);}fn(resolve);
}
  • Promise 有三个变量: 状态 state ,值value和延迟队列deferreds 。
  • 初始的状态是pending ,value是空,延迟队列也是空
  • 传入Promise 的函数fn立即执行,fn执行完成调用resolve
  • resolve方法采用异步方式执行延迟队列里的方法
  • promise对象上添加then方法,当前promise对象状态为pending时,将通过then方法注册的新方法,添加到延迟队列;当前promise对象状态为完成时,执行注册的方法

参考

  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

Promise 最完整介绍与实现解密相关推荐

  1. HTTP状态码完整介绍

    2019独角兽企业重金招聘Python工程师标准>>> HTTP状态码是什么?HTTP状态码有什么用处?如何处理HTTP状态码能够和搜索引擎更友好?技巧在哪里?更有利于网站优化?HT ...

  2. python 超参数_完整介绍用于Python中自动超参数调剂的贝叶斯优化

    完整介绍用于Python中自动超参数调剂的贝叶斯优化-1.jpg (109.5 KB, 下载次数: 0) 2018-7-4 23:45 上传 调剂机器学习超参数是一项繁琐但至关重要的任务,因为算法的性 ...

  3. 加泰罗尼亚理工大学 计算机 排名,西班牙加泰罗尼亚理工大学排名完整介绍

    西班牙加泰罗尼亚理工大学排名完整介绍 加泰罗尼亚理工大学的起源可以追溯到创立于1968年的高级理工学院和创立于1971年的'巴塞罗那理工大学,如今,加泰罗尼亚理工大学已经是西班牙声名最为显赫的高等学府 ...

  4. python初学者_初学者使用Python的完整介绍

    python初学者 A magical art of teaching a computer to perform a task is called computer programming. Pro ...

  5. C++ 开源库,很完整介绍【转】

    [转]http://blog.csdn.net/lixingshi/article/details/22714783 向C++初学者推荐的几个开源库 标签:  STL  boost  SDL  wxW ...

  6. Directshow完整介绍

    最近一段时间,在编写DirectShow应用程序时常常遇到一些问题,原因是对DirectShow技术没有较全面地掌握,对各个接口间的关系以及filter与filter之间连接的内部过程等都只是一知半解 ...

  7. 1.1收集域名信息-完整介绍

    一.域名介绍 域名(Domain Name),是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位. 在最初,没有域名,如果想要访问某一个网 ...

  8. Spring Boot框架敏感信息泄露的完整介绍与SRC实战(附专属字典与PoC)

    转载于:https://www.freebuf.com/vuls/289710.html #前言 ##Spring Boot框架介绍 Spring框架功能很强大,但是就算是一个很简单的项目,我们也要配 ...

  9. agv系统介绍_智能自动化物流系统AGV基础知识(完整介绍)

    大家好,我是李孟宦,智能制造之家号主小智前同事~ AGV作为智能物流系统中不可或缺的一环,一直受到大家所关注, 前面我们介绍了用S7-1200搭建一套完整的AGV控制系统: 如何用S7-1200从零开 ...

最新文章

  1. 通信中dBFS、dBm、dBV、dBW、0dB、-3dB的定义
  2. SQL盲注之时间注入
  3. Golang中常用的并发模型
  4. 怎么用BarTender批量打印标签
  5. android os于8.1区别,Android-x86 8.1-rc2发布 运行于x86 PC上的安卓OS
  6. ChartCtrl源码剖析之——CChartAxis类
  7. pyecharts显示所有x轴_30分钟学会pyecharts数据可视化
  8. 什么软件能打开Android,哪位晓得apk文件用什么软件打开
  9. 字节数组java加密与解密
  10. Windows - 安装/卸载服务 - 学习/实践
  11. 【车标识别】基于SIFT算子的车标识别算法matlab仿真
  12. python---turtle库(详解)
  13. Secure CRT 最大显示行数的设置
  14. windows10桌面壁纸的储存地址在哪里
  15. Microbiome | 黄海所陈松林院士/华科宁康等-肠道菌群在龙利鱼(半滑舌鳎)抗弧菌病性状形成中的机制...
  16. 高级研发工程师都有哪些特点?快来看看你是否符合【超级准】
  17. C. Remove Adjacent
  18. 秦羽接引的鸿蒙第四人,星辰变:秦羽用过的法宝一件比一件厉害,最后一件直接逆天改命!...
  19. 互联网行业中最常用的数据库——MySQL 索引、事务与存储引擎
  20. SAP ABAP——内表(三)【工作区与表头】

热门文章

  1. 杂项:Java un
  2. JMeter如何和Charles进行接口测试
  3. 多个构造器参数使用构建器
  4. ASP.NET MVC RedirectToRoute类[转]
  5. Angular之双向数据绑定(上)
  6. global.css
  7. Python(6)-文件和异常
  8. 手机锁屏js倒计时停止问题解决办法探索
  9. 【方案分享】华为智慧农业解决方案.pdf(附下载链接)
  10. 推荐系统系列教程之十四:经典模型融合方法----线性模型和树模型的组合拳