JS原生引用类型解析7-Promise类型

1. 简介

ES6引入了一个全新的对象Promise,用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。Promise最直接的好处就是链式调用,另外在错误捕获上也很方便。用同步的写法解决异步问题,代码直观,易于理解维护,解决了回调地狱的问题。关于Promise的详细讲解和更多用例我会开专门文章讨论。这里我们主要看一下Promise及其原型的属性和方法。

2. Promise对象创建

Promise对象使用new构造函数创建。基本使用方法如下:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

var promise = new Promise(function(resolve, reject) {
  setTimeout(resolve, 1000, 'foo');
});
promise.then(function(val){
    console.log(val);  // 1000ms后输出 ‘foo’
})

3. Promise构造函数的属性与方法

我们用Object.getOwnPropertyNames()方法获取Promise构造函数的所有属性与方法。

Object.getOwnPropertyNames(Promise);
// (7) ["length", "name", "prototype", "all", "race", "resolve", "reject"]

发现一共有7个属性和方法。

3.1 Promise构造函数的属性

Promise.length 长度总为1 (构造器参数的数目)

Promise.name 名称为"Promise"

Promise.prototype 指向Promise构造函数的原型,可以为所有 Promise 类型的对象添加属性。

3.2 Promise构造函数的方法

Promise.all(iterable) 这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法---MDN Promise译者注)

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

当然,当参数不包含 Promise 时, 该方法返回完成(resolve),但这显然没有什么意义。

Promise.race(iterable) 当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

Promise.reject(reason) 返回一个状态为失败的Promise对象,并将给定的失败信息reason(Promise被拒绝的原因)传递给对应的处理方法。此处使用Error实例的reason对调试和选择性错误捕捉很有帮助。

Promise.reject("Testing static reject").then(function(reason) {
  // 未被调用
}, function(reason) {
  console.log(reason); // "Testing static reject"
});

Promise.reject(new Error("fail")).then(function(error) {
  // 未被调用
}, function(error) {
  console.log(error); // 堆栈跟踪
  /* Error: fail
    at <anonymous>:7:16 */
});

Promise.resolve(value) 返回一个状态由给定value决定的Promise对象。如果该值是一个Promise对象,则直接返回该对象;如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

用法如下:

  1. Promise.resolve(promise); 直接返回该对象promise
  2. Promise.resolve(thenable); 返回一个最终状态由then方法执行决定的Promise对象
  3. Promise.resolve(value) value为空,基本类型,或者不带then方法的对象,返回状态为fulfilled的Promise对象,并且将该value传递给对应的then方法

基本用法示例:

var promise1 = Promise.resolve([1, 2, 3]);

promise1.then(function(value) {
  console.log(value);
  // expected output: Array [1, 2, 3]
});

4. Promise原型对象的属性与方法

我们用Object.getOwnPropertyNames()方法获取Promise原型对象的所有属性与方法。

Object.getOwnPropertyNames(Promise.prototype);
// (4) ["constructor", "then", "catch", "finally"]

发现一共有4个属性和方法。

4.1 Promise原型对象的属性

Promiset.prototype.constructor 指向构造函数Promise

4.2 Promise原型对象的方法

Promise.prototype.then(onFullfilled, onRejected) 它最多需要有两个参数:Promise 的接受(fulfillment)和拒绝(rejection)情况的回调函数。返回一个新的 Promise,该Promise将以回调的返回值来resolve。

语法:

p.then(onFulfilled, onRejected);

p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});

参数:

  • onFulfilled 当Promise变成接受状态(fulfillment)时,该参数作为回调函数被调用。该函数有一个参数,即接受的值(the fulfillment value)。
  • onRejected 当Promise变成拒绝状态(rejection )时,该参数作为回调函数被调用。该函数有一个参数,即拒绝的原因(the rejection reason)。

返回值:

then方法返回一个Promise。而它的行为与then中的回调函数的返回值有关:

  • 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  • 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
  • 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
  • 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

注意:

如果忽略针对某个状态的回调函数参数,或者提供非函数 (nonfunction) 参数,那么 then 方法将会丢失关于该状态的回调函数信息,但是并不会产生错误。如果调用 then 的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。

用法示例:

var promise1 = new Promise(function(resolve, reject) {
  resolve('Success!');
});

promise1.then(function(value) {
  console.log(value);
  // expected output: "Success!"
});

Promise.catch(onRejected) 添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。 语法:

p.catch(onRejected);

p.catch(function(reason) {
   // 拒绝
});

参数:

  • onRejected 当Promise 被拒绝时,被调用的一个Function。该函数拥有一个参数:
  • reason 拒绝的原因。

返回值: 一个Promise。

示例: 有三种常见的使用情况:

  1. 使用链式语句的 catch方法:
var p1 = new Promise(function(resolve, reject) {
  resolve('Success');
});

p1.then(function(value) {
  console.log(value); // "成功!"
  throw 'oh, no!';
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

// 以下行为与上述相同
p1.then(function(value) {
  console.log(value); // "成功!"
  return Promise.reject('oh, no!');
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});
  1. 捕获抛出的错误
// 抛出一个错误,大多数时候将调用catch方法
var p1 = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

p1.catch(function(e) {
  console.log(e); // "Uh-oh!"
});

// 在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    throw 'Uncaught Exception!';
  }, 1000);
});

p2.catch(function(e) {
  console.log(e); // 不会执行
});

// 在resolve()后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {
  resolve();
  throw 'Silenced Exception!';
});

p3.catch(function(e) {
   console.log(e); // 不会执行
});
  1. 如果已决议
//创建一个新的 Promise ,且已决议
var p1 = Promise.resolve("calling next");

var p2 = p1.catch(function (reason) {
    //这个方法永远不会调用
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});

Promise.prototype.finally(onFinally) 添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)

注意: finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:

  • 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
  • 与Promise.resolve(2).then(() => {}, () => {}) (resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {}) resolved的结果为 2。
  • 同样,Promise.reject(3).then(() => {}, () => {}) (resolved 的结果为undefined), Promise.reject(3).finally(() => {}) rejected 的结果为 3。

用法示例: 一个典型的用法,在发出请求时,页面的loading效果开启,然后不管返回的结果是完成(fulfilled)还是失败(rejected),都会执行onFinally将loading效果取消。

let isLoading = true;

fetch(myRequest).then(function(response) {
    var contentType = response.headers.get("content-type");
    if(contentType && contentType.includes("application/json")) {
      return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
  })
  .then(function(json) { /* process your JSON further */ })
  .catch(function(error) { console.log(error); })
  .finally(function() { isLoading = false; });
  1. Promise实例对象的属性与方法 我们用Object.getOwnPropertyNames()方法获取Promise实例对象的所有属性与方法。
var p = new Promise(function(resolve, reject) {
    resolve('success');
})
Object.getOwnPropertyNames(p);  // []

我们发现实例本身没有绑定属性与方法。

参考

MDN-Promise Promise 对象

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏极客慕白的成长之路

Python字符串格式化

格式化字符串时,Python使用一个字符串作为模板。模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式。Python用一个tuple将多个...

1292
来自专栏Golang语言社区

go interface

Go不是一种典型的OO语言,它在语法上不支持类和继承的概念。 没有继承是否就无法拥有多态行为了呢?答案是否定的,Go语言引入了一种新类型—Interface,它...

3325
来自专栏HappenLee的技术杂谈

C++雾中风景6:拷贝构造函数与赋值函数

这里我们显式声明了拷贝构造函数与赋值构造函数,接下来我们用一小段代码测试一下上面定义的类。(其他函数的定义并不完整,读者可以之行补全)

892
来自专栏大闲人柴毛毛

稳扎稳打JavaScript(一)——作用域链内存模型

几个概念 在开始之前,先了解几个概念。 1.1. 作用域 作用域是指当前正在执行的代码能够访问到变量的范围; 每个函数都有各自的作用域,存储函数所有的局部变量...

4618
来自专栏前端小叙

JS中函数声明与函数表达式的异同

相同点 注:函数声明和函数表达式的相同点包括但不限于以下几点 函数是一个值,所以和其他值一样,函数也可以进行被输出、被赋值、作为参数传给其他函数等相关操作,不...

2885
来自专栏java 成神之路

JAVA对象在JVM中内存分配

35012
来自专栏web前端-

函数基础知识回顾

  String    Number   Boolean   Null    undefined    //占有固定的内存大小,如数值型占八个字节,布尔类型...

753
来自专栏大愚Talk

Golang中函数传参存在引用传递吗?

官方文档已经明确说明:Go里边函数传参只有值传递一种方式,为了加强自己的理解,再来把每种传参方式进行一次梳理。

1482
来自专栏梧雨北辰的开发录

Swift学习:构造器(下)

本篇主要介绍Swift中构造器的一些特殊用法 一、可失败的构造器 顾名思义,这是用于我们构造过程可能失败情况的构造器。失败的原因可能是给构造器传入无效的参数值,...

2757
来自专栏AI研习社

最常见的 35 个 Python 面试题及答案(2018 版)

作为一个 Python 新手,你必须熟悉基础知识。在本文中我们将讨论一些 Python 面试的基础问题和高级问题以及答案,以帮助你完成面试。包括 Python ...

7293

扫码关注云+社区

领取腾讯云代金券