Promise使用手册
导读
Promise问世已久, 其科普类文章亦不计其数. 遂本篇初衷不为科普, 只为能够温故而知新.
比如说, catch能捕获所有的错误吗? 为什么有些时候会抛出”Uncaught (in promise) …”? Promise.resolve 和 Promise.reject 处理Promise对象时又有什么不一样的地方?
Promise
引子
阅读此篇之前, 我们先体验一下如下代码:
setTimeout( function () console.log(4) }, new
function (resolve) console.log(1); for
var
i } console.log(2); }).then( function () console.log(5) }); console.log(3); |
这里先卖个关子, 后续将给出答案并提供详细分析.
和往常文章一样, 我喜欢从api入手, 先具象地了解一个概念, 然后再抽象或扩展这个概念, 接着再谈谈概念的具体应用场景, 通常末尾还会有一个简短的小结. 这样, 查询api的读者可以选择性地阅读上文, 希望深入的读者可以继续剖析概念, 当然我更希望你能耐心地读到应用场景处, 这样便能升华对这个概念或技术的运用, 也能避免踩坑.
new Promise
Promise的设计初衷是避免异步回调地狱. 它提供更简洁的api, 同时展平回调为链式调用, 使得代码更加清爽, 易读.
如下, 即创建一个Promise对象:
const new
function (resolve, console.log( 'Create ); }); console.log(p); |
创建Promise时, 浏览器同步执行传入的第一个方法, 从而输出log. 新创建的promise实例对象, 初始状态为等待(pending), 除此之外, Promise还有另外两个状态:
- fulfilled, 表示操作完成, 实现了. 只在resolve方法执行时才进入该状态.
- rejected, 表示操作失败, 拒绝了. 只在reject方法执行时或抛出错误的情况下才进入该状态.
如下图展示了Promise的状态变化过程(图片来自MDN):
从初始状态(pending)到实现(fulfilled)或拒绝(rejected)状态的转换, 这是两个分支, 实现或拒绝即最终状态, 一旦到达其中之一的状态, promise的状态便稳定了. (因此, 不要尝试实现或拒绝状态的互转, 它们都是最终状态, 没法转换)
以上, 创建Promise对象时, 传入的回调函数function(resolve, reject){}
默认拥有两个参数, 分别为:
- resolve, 用于改变该Promise本身的状态为实现, 执行后, 将触发then的onFulfilled回调, 并把resolve的参数传递给onFulfilled回调.
- reject, 用于改变该Promise本身的状态为拒绝, 执行后, 将触发 then | catch的onRejected回调, 并把reject的参数传递给onRejected回调.
Promise的原型仅有两个自身方法, 分别为 Promise.prototype.then
, Promise.prototype.catch
.
而它自身仅有四个方法, 分别为 Promise.reject
, Promise.resolve
, Promise.all
, Promise.race
.
then
语法: Promise.prototype.then(onFulfilled, onRejected)
用于绑定后续操作. 使用十分简单:
p.then( function (res) console.log( '此处执行后续操作' ); }); // p.then( function (res) console.log( '先做一件事' ); }).then( function (res) console.log( '再做一件事' ); }); // p.then( function (SuccessRes) console.log( '处理成功的操作' ); }, function (failRes) console.log( '处理失败的操作' ); }); |
不仅如此, Promise的then中还可返回一个新的Promise对象, 后续的then将接着继续处理这个新的Promise对象.
p.then( function (){ return
function (resolve, console.log( '这里是一个新的Promise对象' ); resolve( 'New ); }); }).then( function (res) console.log(res); }); |
那么, 如果没有指定返回值, 会怎么样?
根据Promise规范, then或catch即使未显式指定返回值, 它们也总是默认返回一个新的fulfilled状态的promise对象.
catch
语法: Promise.prototype.catch(onRejected)
用于捕获并处理异常. 无论是程序抛出的异常, 还是主动reject掉Promise自身, 都会被catch捕获到.
new
function (resolve, reject( '该prormise已被拒绝' ); }). catch ( function (reason) console.log( 'catch:' , }); |
同then语句一样, catch也是可以链式调用的.
new
function (resolve, reject( '该prormise已被拒绝' ); }). catch ( function (reason){ console.log( 'catch:' , console.log(a); }). catch ( function (reason){ console.log(reason); }); |
以上, 将依次输出两次log, 第一次输出promise被拒绝, 第二次输出”ReferenceError a is not defined”的堆栈信息.
catch能捕获哪些错误
那是不是catch可以捕获所有错误呢? 可以, 怎么不可以, 我以前也这么天真的认为. 直到有一天我执行了如下的语句, 我就学乖了.
new
function (resolve, Promise.reject( '返回一个拒绝状态的Promise' ); }). catch ( function (reason){ console.log( 'catch:' , }); |
执行结果如下:
为什么catch没有捕获到该错误呢? 这个问题, 待下一节我们了解了Promise.reject语法后再做分析.
Promise.reject
语法: Promise.reject(value)
该方法返回一个拒绝状态的Promise对象, 同时传入的参数作为PromiseValue.
//params: Promise.reject( '该prormise已被拒绝' ); . catch ( function (reason){ console.log( 'catch:' , }); //params: Promise.reject( new
'这是一个error' )).then( function (res) console.log( 'fulfilled:' , }, function (reason) console.log( 'rejected:' , // }); |
即使参数为Promise对象, 它也一样会把Promise当作拒绝的理由, 在外部再包装一个拒绝状态的Promise对象予以返回.
//params: const new
function (resolve) console.log( 'This ); }); Promise.reject(p). catch ( function (reason) console.log( 'rejected:' , console.log(p }); // // // |
以上代码片段, Promise.reject(p)
进入到了catch语句中, 说明其返回了一个拒绝状态的Promise, 同时拒绝的理由就是传入的参数p.
错误处理
我们都知道, Promise.reject返回了一个拒绝状态的Promise对象. 对于这样的Promise对象, 如果其后续then | catch中都没有声明onRejected回调, 它将会抛出一个 “Uncaught (in promise) …”的错误. 如上图所示, 原语句是 “Promise.reject(‘返回一个拒绝状态的Promise’);” 其后续并没有跟随任何then | catch语句, 因此它将抛出错误, 且该错外部的Promise无法捕获.
不仅如此, Promise之间泾渭分明, 内部Promise抛出的任何错误, 外部Promise对象都无法感知并捕获. 同时, 由于promise是异步的, try catch语句也无法捕获其错误.
因此养成良好习惯, promise记得写上catch.
除了catch, nodejs下Promise抛出的错误, 还会被进程的unhandledRejection
和 rejectionHandled
事件捕获.
链式写法的好处
请看如下代码:
new
function (resolve, resolve( 'New ); }).then( function (str) throw
"oops..." ); }, function (error) console.log( 'then , }). catch ( function (reason) console.log( 'catch:' , }); //catch: |
可见, then语句的onRejected回调并不能捕获onFulfilled回调内抛出的错误, 尾随其后的catch语句却可以, 因此推荐链式写法.
Promise.resolve
语法: Promise.resolve(value | promise | thenable)
thenable 表示一个定义了 then
方法的对象或函数.
参数为promise时, 返回promise本身.
参数为thenable的对象或函数时, 将其then属性作为new promise时的回调, 返回一个包装的promise对象.(注意: 这里与Promise.reject直接包装一个拒绝状态的Promise不同)
其他情况下, 返回一个实现状态的Promise对象, 同时传入的参数作为PromiseValue.
//params: //return: Promise.resolve( '返回一个fulfilled状态的promise' ).then( function (res) console.log(res); // }); //params: //return: Promise.resolve([ 'a' , 'b' , 'c' ]).then( function (res) console.log(res); // }); //params: //return: let const new
function (resolve) resolveFn }); const r2.then( function (res) console.log(res); }); resolveFn( 'xyz' ); // console.log(r2 // //params: //return: const then: function (resolve, //作为new reject( 'promise ); } }; Promise.resolve(thenable).then( function (res) console.log( 'res:' , }, function (reason) console.log( 'reason:' , }); |
可见, Promise.resolve并非返回实现状态的Promise这么简单, 我们还需基于传入的参数动态判断.
至此, 我们基本上不用期望使用Promise全局方法中去改变其某个实例的状态.
- 对于Promise.reject(promise), 它只是简单地包了一个拒绝状态的promise壳, 参数promise什么都没变.
- 对于Promise.resolve(promise), 仅仅返回参数promise本身.
Promise.all
语法: Promise.all(iterable)
该方法接一个迭代器(如数组等), 返回一个新的Promise对象. 如果迭代器中所有的Promise对象都被实现, 那么, 返回的Promise对象状态为”fulfilled”, 反之则为”rejected”. 概念上类似Array.prototype.every.
//params: //return: Promise.all([1, function (res){ console.log( 'promise , // }); //params: //return: const new
function (resolve, reject( 'rejected' ); }); Promise.all([1, function (res){ console.log( 'promise , }). catch ( function (reason){ console.log( 'promise , // }); |
Promise.all特别适用于处理依赖多个异步请求的结果的场景.
Promise.race
该方法接一个迭代器(如数组等), 返回一个新的Promise对象. 只要迭代器中有一个Promise对象状态改变(被实现或被拒绝), 那么返回的Promise将以相同的值被实现或拒绝, 然后它将忽略迭代器中其他Promise的状态变化.
Promise.race([1, function (res){ console.log( 'promise , }). catch ( function (reason){ console.log( 'promise , }); // |
如果调换以上参数的顺序, 结果将输出 “promise reject: 2”. 可见对于状态稳定的Promise(fulfilled 或 rejected状态), 哪个排第一, 将返回哪个.
Promise.race适用于多者中取其一的场景, 比如同时发送多个请求, 只要有一个请求成功, 那么就以该Promise的状态作为最终的状态, 该Promise的值作为最终的值, 包装成一个新的Promise对象予以返回.
在 Fetch进阶指南 一文中, 我曾利用Promise.race模拟了Promise的abort和timeout机制.
Promises/A+规范的要点
promise.then(onFulfilled, onRejected)中, 参数都是可选的, 如果onFulfilled或onRejected不是函数, 那么将忽略它们.
catch只是then的语法糖, 相当于promise.then(null, onRejected).
任务队列之谜
终于, 我们要一起来看看文章起始的一道题目.
setTimeout( function () console.log(4) }, new
function (resolve) console.log(1); for
var
i } console.log(2); }).then( function () console.log(5) }); console.log(3); |
这道题目来自知乎(机智的你可能早已看穿, 但千万别戳破
Promise使用手册的更多相关文章
- 【转】ES6 手册
目录 var 和 let/const 的比较 用块级作用域代替 IIFES 箭头函数 字符串 解构 模块 参数 类 Classes Symbols Maps WeakMaps Promises Gen ...
- 【转】Unity中的协同程序-使用Promise进行封装(一)
原文:http://gad.qq.com/program/translateview/7170767 译者:陈敬凤(nunu) 审校:王磊(未来的未来) 每个Unity的开发者应该都对协同程序非 ...
- 大神都在看的RxSwift 的完全入坑手册
大神都在看的RxSwift 的完全入坑手册 2015-09-24 18:25 CallMeWhy callmewhy 字号:T | T 我主要是通过项目里的 Rx.playground 进行学习和了解 ...
- JS 异步系列 —— Promise 札记
Promise 研究 Promise 的动机大体有以下几点: 对其 api 的不熟悉以及对实现机制的好奇; 很多库(比如 fetch)是基于 Promise 封装的,那么要了解这些库的前置条件得先熟悉 ...
- Egret 菜鸟级使用手册
首先,先安装好,然后,创建项目,弄好之后,在终端输入 egret run -a 开启服务 /*********************************华丽丽的分割线************** ...
- ES6 完全使用手册
前言 这里的 "ES6" 泛指 ES5 之后的新语法 这里的 "完全" 是指本文会不断更新 这里的 "使用" 是指本文会展示很多 ES6 的 ...
- Vert.x Core 文档手册
Vert.x Core 文档手册 中英对照表 Client:客户端 Server:服务器 Primitive:基本(描述类型) Writing:编写(有些地方译为开发) Fluent:流式的 Reac ...
- hydra-microservice 中文手册(3W字预警)
Hydras 是什么? Hydra 是一个 NodeJS 包(技术栈不是重点,思想!思想!思想!),它有助于构建分布式应用程序,比如微服务. Hydra 提供服务发现(service discover ...
- ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(中)
快速上手多人游戏服务器开发.后续会基于 Google Agones,更新相关 K8S 运维.大规模快速扩展专用游戏服务器的文章.拥抱️原生 Cloud-Native! 系列 ColyseusJS 轻量 ...
随机推荐
- Hibernate知识点小结(三)-->一对多与多对多配置
一.多表关系与多表设计 1.多表关系 一对一: 表的设计原则(分表原则): 优化表的性能 基于语意化分表 ...
- C++新闻检索类
研究长字符串快速全文检索技术,实现某电力公司新闻中心新闻稿件全文检索统计系统. 1. 设计实现适合新闻稿件的基础类库 2. 新闻稿件全文检索功能实现 3. 新闻稿件按照关键字统计查询 代码如下 P ...
- 【PTA 天梯赛训练】电话聊天狂人(简单map)
输入格式: 输入首先给出正整数N(≤10^5),为通话记录条数.随后N行,每行给出一条通话记录.简单起见,这里只列出拨出方和接收方的11位数字构成的手机号码,其中以空格分隔. 输出格式: 在一行中给出 ...
- 小胖办证 wzoi
小胖办证 题目描述: xuzhenyi要办个签证.办证处是一座M层的大楼,1<=M<=100. 每层楼都有N个办公室,编号为1..N(1<=N<=500).每个办公室有一个签证 ...
- Object.keys方法
我们有时需要知道对象的所有属性,原生js给我们提供了一个很好的方法:Object.keys(),该方法返回一个数组 传入对象,返回属性名 var obj = {'a':'123','b':'345'} ...
- PHP队列之理论篇
定义: 特殊的线性表. 特点: 1.先进先出:连结性. 2.作为一种特殊性的表,主要是在表前端进行删除操作,我们称删除的端为对头(front):只能在表的后端进行插入操作,我们称之为称插入 ...
- Scrapy框架的基本使用
安装 pip install scrapy 基础使用 1. 创建一个工程:scrapy startproject 2. 在工程目录下创建一个爬虫文件 cd 工程 scrapy genspider 爬虫 ...
- unity独立游戏开发日志2018/09/22
f::很头痛之前rm做的游戏在新电脑工程打不开了...只能另起炉灶... 还不知道新游戏叫什么名...暂且叫方块世界.(素材已经授权) 首先是规划下场景和素材文件夹的建立. unity常用的文件夹有: ...
- UVA11988 Broken Keyboard (a.k.a. Beiju Text)【数组模拟链表】
参考:https://blog.csdn.net/lianai911/article/details/41831645 #include <iostream> #include <c ...
- 用 Qt 控制 Nikon 显微镜的电动物镜转盘
用 Qt 控制 Nikon 显微镜的电动物镜转盘 最近的一个项目,用到了一台 Nikon 的金相显微镜,并且配了个电动的物镜转盘.为了控制这个电动物镜转盘,我折腾了差不多有4-5天.中间遇到了各种问题 ...