导读

Promise问世已久, 其科普类文章亦不计其数. 遂本篇初衷不为科普, 只为能够温故而知新.

比如说, catch能捕获所有的错误吗? 为什么有些时候会抛出”Uncaught (in promise) …”? Promise.resolve 和 Promise.reject 处理Promise对象时又有什么不一样的地方?

Promise

引子

阅读此篇之前, 我们先体验一下如下代码:

setTimeout(function()
{
  console.log(4)
},
0);
new

Promise(
function(resolve)
{
  console.log(1);
  for

(
var

i = 0; i < 10000; i++) {
    i
== 9999 && resolve()
  }
  console.log(2);
}).then(function()
{
  console.log(5)
});
console.log(3);

这里先卖个关子, 后续将给出答案并提供详细分析.

和往常文章一样, 我喜欢从api入手, 先具象地了解一个概念, 然后再抽象或扩展这个概念, 接着再谈谈概念的具体应用场景, 通常末尾还会有一个简短的小结. 这样, 查询api的读者可以选择性地阅读上文, 希望深入的读者可以继续剖析概念, 当然我更希望你能耐心地读到应用场景处, 这样便能升华对这个概念或技术的运用, 也能避免踩坑.

new Promise

Promise的设计初衷是避免异步回调地狱. 它提供更简洁的api, 同时展平回调为链式调用, 使得代码更加清爽, 易读.

如下, 即创建一个Promise对象:

const
p =
new

Promise(
function(resolve,
reject) {
  console.log('Create
a new Promise.'
);
});
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('此处执行后续操作');
});
//
当然, then的最大便利之处便是可以链式调用
p.then(function(res)
{
  console.log('先做一件事');
}).then(function(res)
{
  console.log('再做一件事');
});
//
then还可以同时接两个回调,分别处理成功和失败状态
p.then(function(SuccessRes)
{
  console.log('处理成功的操作');
},
function(failRes)
{
  console.log('处理失败的操作');
});

不仅如此, Promise的then中还可返回一个新的Promise对象, 后续的then将接着继续处理这个新的Promise对象.

p.then(function(){
  return

new

Promise(
function(resolve,
reject) {
    console.log('这里是一个新的Promise对象');
    resolve('New
Promise resolve.'
);
  });
}).then(function(res)
{
  console.log(res);
});

那么, 如果没有指定返回值, 会怎么样?

根据Promise规范, then或catch即使未显式指定返回值, 它们也总是默认返回一个新的fulfilled状态的promise对象.

catch

语法: Promise.prototype.catch(onRejected)

用于捕获并处理异常. 无论是程序抛出的异常, 还是主动reject掉Promise自身, 都会被catch捕获到.

new

Promise(
function(resolve,
reject) {
  reject('该prormise已被拒绝');
}).catch(function(reason)
{
  console.log('catch:',
reason);
});

同then语句一样, catch也是可以链式调用的.

new

Promise(
function(resolve,
reject){
  reject('该prormise已被拒绝');
}).catch(function(reason){
  console.log('catch:',
reason);
  console.log(a);
}).catch(function(reason){
  console.log(reason);
});

以上, 将依次输出两次log, 第一次输出promise被拒绝, 第二次输出”ReferenceError a is not defined”的堆栈信息.

catch能捕获哪些错误

那是不是catch可以捕获所有错误呢? 可以, 怎么不可以, 我以前也这么天真的认为. 直到有一天我执行了如下的语句, 我就学乖了.

new

Promise(
function(resolve,
reject){
  Promise.reject('返回一个拒绝状态的Promise');
}).catch(function(reason){
  console.log('catch:',
reason);
});

执行结果如下:

为什么catch没有捕获到该错误呢? 这个问题, 待下一节我们了解了Promise.reject语法后再做分析.

Promise.reject

语法: Promise.reject(value)

该方法返回一个拒绝状态的Promise对象, 同时传入的参数作为PromiseValue.

//params:
String
Promise.reject('该prormise已被拒绝');
.catch(function(reason){
  console.log('catch:',
reason);
});
//params:
Error
Promise.reject(new

Error(
'这是一个error')).then(function(res)
{
  console.log('fulfilled:',
res);
},
function(reason)
{
  console.log('rejected:',
reason);
//
rejected: Error: 这是一个error...
});

即使参数为Promise对象, 它也一样会把Promise当作拒绝的理由, 在外部再包装一个拒绝状态的Promise对象予以返回.

//params:
Promise
const
p =
new

Promise(
function(resolve)
{
  console.log('This
is a promise'
);
});
Promise.reject(p).catch(function(reason)
{
  console.log('rejected:',
reason);
  console.log(p
== reason);
});
//
"This is a promise"
//
rejected: Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
//
true

以上代码片段, 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

Promise(
function(resolve,
reject) {
  resolve('New
Promise resolve.'
);
}).then(function(str)
{
  throw

new

Error(
"oops...");
},function(error)
{
    console.log('then
catch:'
,
error);
}).catch(function(reason)
{
    console.log('catch:',
reason);
});
//catch:
Error: oops...

可见, 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:
String
//return:
fulfilled Promise
Promise.resolve('返回一个fulfilled状态的promise').then(function(res)
{
  console.log(res);
//
"返回一个fulfilled状态的promise"
});
//params:
Array
//return:
fulfilled Promise
Promise.resolve(['a',
'b',
'c']).then(function(res)
{
  console.log(res);
//
["a", "b", "c"]
});
//params:
Promise
//return:
Promise self
let
resolveFn;
const
p2 =
new

Promise(
function(resolve)
{
  resolveFn
= resolve;
});
const
r2 = Promise.resolve(p2);
r2.then(function(res)
{
  console.log(res);
});
resolveFn('xyz');
//
"xyz"
console.log(r2
=== p2);
//
true
//params:
thenable Object
//return:
根据thenable的最终状态返回不同的promise
const
thenable = {
  then:
function(resolve,
reject) {
//作为new
promise时的回调函数
    reject('promise
rejected!'
);
  }
};
Promise.resolve(thenable).then(function(res)
{
  console.log('res:',
res);
},
function(reason)
{
  console.log('reason:',
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:
all fulfilled promise
//return:
fulfilled promise
Promise.all([1,
2, 3]).then(
function(res){
  console.log('promise
fulfilled:'
,
res);
//
promise fulfilled: [1, 2, 3]
});
//params:
has rejected promise
//return:
rejected promise
const
p =
new

Promise(
function(resolve,
reject){
  reject('rejected');
});
Promise.all([1,
2, p]).then(
function(res){
  console.log('promise
fulfilled:'
,
res);
}).catch(function(reason){
  console.log('promise
reject:'
,
reason);
//
promise reject: rejected
});

Promise.all特别适用于处理依赖多个异步请求的结果的场景.

Promise.race

该方法接一个迭代器(如数组等), 返回一个新的Promise对象. 只要迭代器中有一个Promise对象状态改变(被实现或被拒绝), 那么返回的Promise将以相同的值被实现或拒绝, 然后它将忽略迭代器中其他Promise的状态变化.

Promise.race([1,
Promise.reject(2)]).then(
function(res){
  console.log('promise
fulfilled:'
,
res);
}).catch(function(reason){
  console.log('promise
reject:'
,
reason);
});
//
promise fulfilled: 1

如果调换以上参数的顺序, 结果将输出 “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)
},
0);
new

Promise(
function(resolve)
{
  console.log(1);
  for

(
var

i = 0; i < 10000; i++) {
    i
== 9999 && resolve()
  }
  console.log(2);
}).then(function()
{
  console.log(5)
});
console.log(3);

这道题目来自知乎(机智的你可能早已看穿, 但千万别戳破

Promise使用手册的更多相关文章

  1. 【转】ES6 手册

    目录 var 和 let/const 的比较 用块级作用域代替 IIFES 箭头函数 字符串 解构 模块 参数 类 Classes Symbols Maps WeakMaps Promises Gen ...

  2. 【转】Unity中的协同程序-使用Promise进行封装(一)

    原文:http://gad.qq.com/program/translateview/7170767 译者:陈敬凤(nunu)    审校:王磊(未来的未来) 每个Unity的开发者应该都对协同程序非 ...

  3. 大神都在看的RxSwift 的完全入坑手册

    大神都在看的RxSwift 的完全入坑手册 2015-09-24 18:25 CallMeWhy callmewhy 字号:T | T 我主要是通过项目里的 Rx.playground 进行学习和了解 ...

  4. JS 异步系列 —— Promise 札记

    Promise 研究 Promise 的动机大体有以下几点: 对其 api 的不熟悉以及对实现机制的好奇; 很多库(比如 fetch)是基于 Promise 封装的,那么要了解这些库的前置条件得先熟悉 ...

  5. Egret 菜鸟级使用手册

    首先,先安装好,然后,创建项目,弄好之后,在终端输入 egret run -a 开启服务 /*********************************华丽丽的分割线************** ...

  6. ES6 完全使用手册

    前言 这里的 "ES6" 泛指 ES5 之后的新语法 这里的 "完全" 是指本文会不断更新 这里的 "使用" 是指本文会展示很多 ES6 的 ...

  7. Vert.x Core 文档手册

    Vert.x Core 文档手册 中英对照表 Client:客户端 Server:服务器 Primitive:基本(描述类型) Writing:编写(有些地方译为开发) Fluent:流式的 Reac ...

  8. hydra-microservice 中文手册(3W字预警)

    Hydras 是什么? Hydra 是一个 NodeJS 包(技术栈不是重点,思想!思想!思想!),它有助于构建分布式应用程序,比如微服务. Hydra 提供服务发现(service discover ...

  9. ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(中)

    快速上手多人游戏服务器开发.后续会基于 Google Agones,更新相关 K8S 运维.大规模快速扩展专用游戏服务器的文章.拥抱️原生 Cloud-Native! 系列 ColyseusJS 轻量 ...

随机推荐

  1. c# 调用服务返回结果模板化

    一般我们返回一个结果,主要有返回值,执行结果信息,所以定义一个类 public  class QuestResult    { /// <summary>        /// 返回值  ...

  2. .net mvc里AutoMapper更为便捷的使用方法

    前言:AutoMapper的下载安装我就不多说了,网上百度一大堆.今天我就说说它的更为简单的使用,什么叫更为简单呢?按照一般的使用方法,我们首先建DTO,然后建每个对应的Profile,然后还要把每个 ...

  3. Git工作流指南:Gitflow工作流

    git工作流 1.Git flow 核心分支:master,dev 可能还会有:功能分支,bug修复分支,预发布分支 2.github flow:只一个长期分支,就是master 第一步:根据需求,从 ...

  4. C/C++使用keybd_event模拟键盘按键

    #include <stdio.h> #include <Windows.h> /* 设置键盘大小写状态 big:为TRUE则切换大写状态,否则切换小写状态 */ VOID M ...

  5. solr索引大小对比

    原文本 Solr建立的索引 如果进行Mysql索引应该是1:3的比例

  6. ubuntu系统快速搭建开发环境

    1.免密登陆 1.1 原理 ssh协议中用到了对称加密和非对称加密,如果不了解可以百度一下,原理引用一下这篇博客 在ssh中,非对称加密被用来在会话初始化阶段为通信双方进行会话密钥的协商.由于非对称加 ...

  7. kali2.0 设置输入法 找了好久,亲测有效

    kali2.0更新源启用中文输入法 查看版本信(Version): uname -r uname -r 工具(Tools): fcitx fcitx fcitx-table-wbpy 更新源:(Sou ...

  8. C#在textBox中输出一个数组

    //将数组输出到文本框测试 for(i=0;i<arr.Length-1;i++){ this.textBox1.Text=this.textBox1.Text+arr[i]; }

  9. hive 学习系列四(用户自定义函数)

    如果入参是简单的数据类型,直接继承UDF,实现一个或者多个evaluate 方法. 具体流程如下: 1,实现大写字符转换成小写字符的UDF package com.example.hive.udf; ...

  10. rails小技巧之分组查询统计并去重

    分组查询并统计 SpecialGroup.group(:special_type).count select special_type,count(*) from special_groups gro ...