读书笔记:深入理解ES6(十一)

原创
2019/10/22 12:07
阅读数 101

第十一章 Promise与异步编程

  Promise可以实现其他语言中类似Future和Deferred一样的功能,是另一种异步编程的选择,它既可以像事件和回调函数一样指定稍后执行的代码,也可以明确指示代码是否成功执行。

 

第1节 异步编程的背景知识

  1. 机制

    JavaScript引擎是基于单线程(Single-threaded)事件循环的概念构建,即同一时刻只允许一个代码块在执行。这些代码块被放在一个任务队列(job queue)中,每当一段代码准备执行时,都会被添加到任务队列。每当JavaScript引擎中的一段代码结束执行,事件循环(event loop)会执行队列中下一个任务。事件循环是JavaScript引擎的一段代码,负责监控代码执行并管理任务队列。

  2. 事件模型

    例如点击按钮或者按下键盘按键会触发的onclick事件,是JavaScript中最基础的异步编程形式。尽管事件模型适用于响应用户交互和完成类似的低频功能,但对更复杂的需求来说却不是很灵活。

  3. 回调模式

    回调模式和事件模型类似,异步代码都会在未来的某个时间点执行,二者的区别是回调模式中被调用的函数是作为参数传入的。例如:

复制代码
1 readFile("example.txt", function(err, contents) {
2     if (err) {
3         throw err;
4     }
5     console.log(contents);
6 });
7 console.log("Hi");
复制代码

    相比之下,回调模式比事件模型更灵活,但想要实现更复杂的功能时,回调函数的局限性同样会显现出来。

 

第2节 Promise的基础知识

  Promise相当于异步操作结果的占位符,它不去订阅事件,也不会传递一个回调函数给目标参数,而是让函数返回一个Promise。例如:

1 //readFile承诺将在未来某一个时刻完成
2 let promise = readFile("example.txt");

  1. Promise的声明周期

    a) Promise先是处于进行中(pending)的状态,此时操作尚未完成;等异步操作执行结束后,Promise变为已处理(settled)状态。分为如下两种状态:

    

    b)Promise的状态改变时,调用then()方法来采取特定的行动。

      then()方法接受2个参数:第一个参数是变为fulfilled时要调用的函数,第2个是状态变为rejected时需要调用的函数。

      Promise还有一个catch()方法,相当于只给其传入拒绝处理程序的then()方法。

 

  2. 创建未完成的Promise

    用Promise构造函数可以创建新的Promise,构造函数只接受一个参数:包含初始化Promise代码的执行器(executor)函数。执行器接受2个参数,分别是执行成功完成时调用的resolve()函数,执行失败时,调用reject()函数。

    执行器函数会立即执行,然后才执行后续流程中的代码(即resolve() / reject()会放到任务队列中再执行)。

 

  3. 创建已处理的Promise

    使用Promise.resolve() / Promise.reject()来实现根据特定的值来创建已解决的Promise。例如:

复制代码
1 let promise = Promise.resolve(42);
2 promise.then(function(value) {
3     console.log(value); //42
4 });
5 
6 let promise = Promise.reject(42);
7 promise.catch(function(value){
8     console.log(value); // 42
9 });
复制代码

 

  4. 执行器错误

    每个执行器中都隐含一个try-catch块,所以错误会被捕获并传入拒绝处理程序。

 

第3节 全局的Promise拒绝处理

  如果在没有拒绝处理程序的情况下,拒绝一个Promise,那么不会提示信息。Promise的特性决定了很难检测一个Promise是否被处理过。Node.js和浏览器分别做了一些改变来解决开发者这个痛点。

  1. Node.js环境的拒绝处理

    在Node.js中,处理Promise拒绝时会触发Promise对象上的两个事件:

    ·unhandledRejection  在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时,触发该事件。

    ·rejectionHandled  在一个事件循环后,当Promise被拒绝,若拒绝处理程序被调用,触发该事件。

    具体代码参考P.249

 

  2. 浏览器环境的拒绝处理

    a) 浏览器也是通过触发两个事件来识别未处理的拒绝的,虽然这些事件是在window对象上触发的,但实际上与Node.js中完全等效。

      ·unhandledRejection  在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时,触发该事件。

      ·rejectionHandled  在一个事件循环后,当Promise被拒绝,若拒绝处理程序被调用,触发该事件。

    b) 在Node.js实现中,事件处理程序接受多个参数;而在浏览器中,事件处理程序接受一个有以下属性的事件对象作为参数:

      ·type  事件名称("unhandledReject"或“rejectionHandled”);

      ·promise  被拒绝的Promise对象

      ·reason  来自Promise的拒绝值

    c) 浏览器实现的另一处不同是,在两个事件中都可以使用拒绝值(reason)。代码参考:P.251

 

第4节 串联Promise

  记住一个原则:只有当第一个Promise完成或被拒绝后,第二个才会被解决。

  1. 捕获错误

    在完成处理程序和拒绝处理程序中可能也会发生错误,而Promise链可以用来捕获这些错误。

    链式Promise调用可以感知到链中其他Promise的错误。

 

  2. Promise链的返回值

     Promise链的另一个重要特性是可以给下游Promise传递数据。在完成处理程序和拒绝处理程序中都可以这么做。

       代码参考P.255

 

  3. 在Promise链中返回Promise

    先定义的Promise的执行器先执行,后定义的后执行。

 

第5节 响应多个Promise

  之前讲的都是单Promise响应。如果想通过监听多个Promise来决定下一步的操作,可以使用ES6提供的Promise.all()和Promise.race()两个方法来监听多个Promise。

  1. Promise.all()方法

    该方法只接受一个参数并返回一个Promise,该参数是一个含有多个受监视Promise的可迭代对象,只有当可迭代对象中所有Promise都被解决后,返回的Promise才会被解决,只有当可迭代对象中所有Promise都被完成后返回的Promise才会被完成。

    其中,有两种情况:

    

 

     我们看看这两种情况,分别怎么处理:

       1. 当迭代对象中所有的Promise都被解决并返回后,最后的Promise里面存的值按照传入参数数组中的Promise的顺序储存。例如:

复制代码
 1 let p1 = new Promise(function(reolve, reject) {
 2     resolve(42);
 3 });
 4 
 5 let p2 = new Promise(function(resolve, reject) {
 6     resolve(43);
 7 });
 8 
 9 let p3 = new Promise(function(resolve, reject) {
10     resolve(44);
11 });
12 
13 let p4 = Promise.all([p1, p2, p3]);
14 
15 p4.then(function(value) {
16     console.log( Array.isArray(value) ); // true
17     console.log( value[0] ); // 42
18     console.log( value[1] ); // 43
19     console.log( value[2] ); // 44
20 });
复制代码

      2.当迭代对象中有被拒绝的Promise时,只要有一个被拒绝,那么返回的Promise没等所有Promise都完成就立即被拒绝,例如:

复制代码
 1 let p1 = new Promise(function(reolve, reject) {
 2     resolve(42);
 3 });
 4 
 5 let p2 = new Promise(function(resolve, reject) {
 6     reject(43);
 7 });
 8 
 9 let p3 = new Promise(function(resolve, reject) {
10     resolve(44);
11 });
12 
13 let p4 = Promise.all([p1, p2, p3]);
14 
15 p4.then(function(value) {
16     console.log( Array.isArray(value) ); // true
17     console.log( value ); // 43
18 });
复制代码

 

  2. Promise.race()方法

    Promise.race()与Promise.all()稍有不同。在可迭代对象中,只要有一个Promise被解决,返回的Promise就解决,无须等到所有Promise都被完成。

    如果先解决的是已完成Promise,则返回已完成Promise;如果先解决的是已拒绝的Promise,则返回已拒绝Promise。看个例子:

复制代码
 1 let p1 = new Promise(function(resolve, reject) {
 2     setTimeout(function() {resolve(42);}, 0);
 3 });
 4 
 5 let p2 = Promise.reject(43);
 6 
 7 let p3 = new Promise(function(resolve,reject) {
 8     resolve(44)
 9 });
10 
11 let p4 = Promise.race([p1, p2, p3]);
12 
13 p4.catch(function(value) {
14     console.log(value); // 43
15 });
复制代码

 

第6节 自Promise继承

  Promise也是基类,因为也可以派生其它类。

  例子参考代码P.262

 

第7节 基于Promise的异步任务执行

  只要每个异步操作都返回Promise,以Promise作为通用接口用于所有异步代码可以简化任务执行器。

  例子参考代码P.265

 

(本节完)

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部