ES快速入门(四)Promise与异步编程

Promise与异步编程

一、JS异步编程背景

JS引擎建立在单线程事件循环的概念上,JS引擎在同一时刻只能执行一段代码。

每当一段代码准备被执行时,它就会被添加到作业队列。当JS引擎结束当前代码

执行后,事件循环就会执行队列中的下一个作业。事件循环(event loop)是JS

引擎的一个内部处理线程,能监视代码的执行并管理作业队列。

1.事件模型

当用户点击一个按钮或按下键盘上的一个键时,一个事件(event ——例如 onclick ——
就被触发了。该事件可能会对此交互进行响应,从而将一个新的作业添加到作业队列的尾
部。这就是 JS 关于异步编程的最基本形式。事件处理程序代码直到事件发生后才会被执行,
此时它会拥有合适的上下文。例如:

    let button = document.getElementById('my-btn');
    //当 button 被点击,赋值给 onclick 的函数就被添加到作业队列的尾部,
    // 并在队列前部所有任务结束之后再执行
    button.onclick = function() {
        console.log('Clicked!!!!!!!!')
    }

缺点:不够灵活。

2.回调模式

回调函数模式类似于事件模型,因为异步代码也会在后面的一个时间点才执行。

不同之处在于需要调用的函数(即回调函数) 是作为参数传入的,如下所示:

readFile("example.txt", function(err, contents) {
//node.js惯例,错误优先的回调函数风格
if (err) { throw err; } console.log(contents); }); console.log("Hi!");

使用回调函数模式, readFile() 会立即开始执行,并在开始读取磁盘时暂停。这意味着console.log("Hi!")

会在 readFile() 被调用后立即进行输出,要早于console.log(contents) 的打印操作。当 readFile() 结束操作后,

它会将回调函数以及相关参数作为一个新的作业添加到作业队列的尾部。在之前的作业全部结束后,该作业才会执行。

缺点:

1)回调地狱(callback hell)

2)无法执行更复杂的操作,例如两个异步操作并行运行,同时启动两个异步操作

二、Promise基础

1.概念

Promise 是为异步操作的结果所准备的占位符。函数可以返回一个 Promise,而不必订阅一个
事件或向函数传递一个回调参数,就像这样:

// readFile 承诺会在将来某个时间点完成
let promise = readFile("example.txt");

在此代码中, readFile() 实际上并未立即开始读取文件,这将会在稍后发生。此函数反而
会返回一个 Promise 对象以表示异步读取操作,因此你可以在将来再操作它。你能对结果进
行操作的确切时刻,完全取决于 Promise 的生命周期是如何进行的。 

2.Promise生命周期

每个 Promise 都会经历一个短暂的生命周期,初始为挂起态(pending state) ,这表示异步
操作尚未结束。一个挂起的 Promise 也被认为是未决的(unsettled ) 。 一旦异步操作结束,
Promise就会被认为是已决的(settled ) ,并进入两种可能状态之一:

1. 已完成(fulfilled ) : Promise 的异步操作已成功结束;
2. 已拒绝(rejected ) : Promise 的异步操作未成功结束, 可能是一个错误,或由其他原因导致。

使用 then() 方法在 Promise 的状态改变时执行一些特定操作 。

then() 方法在所有的 Promise 上都存在,并且接受两个参数。第一个参数是 Promise 被完
成时要调用的函数,与异步操作关联的任何附加数据都会被传入这个完成函数。第二个参数
则是 Promise 被拒绝时要调用的函数,与完成函数相似,拒绝函数会被传入与拒绝相关联的
任何附加数据。用这种方式实现 then() 方法的任何对象都被称为一个 thenable 。所有的

Promise 都是thenable ,反之则未必成立。传递给 then() 的两个参数都是可选的,因此你可

以监听完成与拒绝的任意组合形式。例如,研究这组 then() 调用:

let promise = readFile("example.txt");
promise.then(function(contents) {
    // 完成
    console.log(contents);
}, function(err) {
    // 拒绝
    console.error(err.message);
});
promise.then(function(contents) {
    // 完成
    console.log(contents);
});
promise.then(null, function(err) {
    // 拒绝
    console.error(err.message);
});

Promis 也具有一个 catch() 方法,其行为等同于只传递拒绝处理函数给 then() 。

只需知道若你未给Promise 附加拒绝处理函数,所有的错误就会静默发生

建议始终附加一个拒绝处理函数,即使该处理程序只是用于打印错误日志。

即使完成或拒绝处理函数在 Promise 已经被解决之后才添加到作业队列,它们仍然会被执
行。这允许你随时添加新的完成或拒绝处理函数,并保证它们会被调用。例如:

let promise = readFile("example.txt");
// 原始的完成处理函数
promise.then(function(contents) {
    console.log(contents);
    // 现在添加另一个
promise.then(function(contents) {
    console.log(contents);
    });
});

在此代码中,完成处理函数又为同一个 Promise 添加了另一个完成处理函数。这个 Promise
此刻已经完成了,因此新的处理程序就被添加到任务队列,并在就绪时(前面的作业执行完
毕后) 被调用。拒绝处理函数使用同样方式工作。

3.创建未决的Promise

新的 Promise 使用 Promise 构造器来创建。此构造器接受单个参数:一个被称为执行器(
executor )的函数,包含初始化 Promise 的代码。该执行器会被传递两个名为 resolve()
reject() 的函数作为参数。 resolve() 函数在执行器成功结束时被调用,用于示意该
Promise 已经准备好被决议(resolved ) ,而 reject() 函数则表明执行器的操作已失败。

    let fs = require("fs");
    function readFile(filename) {
        return new Promise(function (resolve, reject) {
            // 触发异步操作
            fs.readFile(filename, {encoding: "utf8"}, function (err, contents) {
                // 检查错误
                if (err) {
                    reject(err);
                    return;
                }
                // 读取成功
                resolve(contents);
            });
        });
    }
    let promise = readFile("example.txt");
    promise.then(function (contents) {
        console.log(contents);
    }, function (err) {
        console.error(err.message);
    });
let promise = new Promise(function (resolve, reject) {
    console.log("执行器被执行");
    resolve();
});
promise.then(function () {
   console.log("then the resolved!");
});
console.log("Hello!!!");
//执行器被执行 
//Hello!!! 
//then the resolved!

4.创建已决的Promise

let promise = Promise.resolve(42);
promise.then(function(value) {
    console.log(value); // 42
});

一个拒绝处理函数被添加到此 Promise ,该拒绝处理函数将永不会被调用,因为此 Promise绝不可能再是拒绝态。

创建一个已经拒绝的Promise:

let promise = Promise.reject(42);
promise.catch(function(value) {
    console.log(value); // 42
});

5.非Promise的Thenable

当一个对象拥有一个能接受 resolve reject 参数的 then() 方法,该对象就会被认为是
一个非 Promise thenable ,就像这样:

    let thenable = {
        then: function(resolve, reject) {
            resolve(42);
        }
    };

Promise.resolve() Promise.reject() 都能接受非 Promise thenable 作为参数。当传
入了非 Promise thenable 时,这些方法会创建一个新的 Promise ,此 Promise 会在then() 函数之后被调用。

let thenable = {
        then: function (resolve, reject) {
            resolve('Resolved the problem!!');
        }
    };
    let p = Promise.resolve(thenable);
    p.then(function (value) {
        console.log(value);
    });

6.执行器错误

如果在执行器内部抛出了错误,那么 Promise 的拒绝处理函数就会被调用。例如:

    let promise = new Promise(function(resolve, reject) {
        throw new Error("Explosion!");
    });
    promise.catch(function(error) {
        console.log(error.message); // "Explosion!"
    });

在此代码中,执行器故意抛出了一个错误。此处在每个执行器之内并没有显式的 try-catch
,因此错误就被捕捉并传递给了拒绝处理函数。这个例子等价于:

   let promise = new Promise(function(resolve, reject) {
        try {
            throw new Error("Explosion!");
        } catch (ex) {
            reject(ex);
        }
    });
    promise.catch(function(error) {
        console.log(error.message); // "Explosion!"
    });

执行器处理程序捕捉了抛出的任何错误,以简化这种常见处理。但在执行器内抛出的错误仅
当存在拒绝处理函数时才会被报告,否则这个错误就会被隐瞒。这在开发者早期使用
Promise 的时候是一个问题,但 JS 环境通过提供钩子(hook ) 来捕捉被拒绝的 Promise
从而解决了此问题。

三、全局的Promise拒绝处理

 1.Node.js的拒绝处理

在node.js中,process对象上存在两个关联到Promise的拒绝处理事件:

1)unhandleRejection:当一个Promise被拒绝、而在事件循环的一个轮次中

没有任何拒绝处理函数被调用,该事件就会被触发。该函数接受被拒绝的原因,

以及被拒绝的Promise。

let rejected;
process.on("unhandledRejection", function(reason, promise) {
   console.log(reason.message); // "Explosion!"
   console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));

2)rejectionHandle: 若一个Promise被拒绝、并在事件循环的一个轮次之后

再有拒绝处理函数被调用,该事件就会被触发。该函数只有一个参数,即已经

被拒绝的的Promise。如:

let rejected;
process.on("rejectionHandled", function(promise) {
    console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
// 延迟添加拒绝处理函数
setTimeout(function() {
    rejected.catch(function(value) {
    console.log(value.message); // "Explosion!"
});
}, 1000);

此处的 rejectionHandled 事件在拒绝处理函数最终被调用时触发。若在 rejected 被创建后
直接将拒绝处理函数附加到它上面,那么此事件就不会被触发。因为立即附加的拒绝处理函
数在 rejected 被创建的事件循环的同一个轮次内就会被调用,这样 rejectionHandled 就不
会起作用。
为了正确追踪潜在的未被处理的拒绝,使用 rejectionHandled unhandledRejection 事件
就能保持包含这些 Promise 的一个列表,之后等待一段时间再检查此列表。例如:

2.浏览器的拒绝处理

  let rejected;
  window.onunhandledrejection = function(event) {
    console.log(event.type); // "unhandledrejection"
    console.log(event.reason.message); // "Explosion!"
    console.log(rejected === event.promise); // true
  };
  window.onrejectionhandled = function(event) {
    console.log(event.type); // "rejectionhandled"
    console.log(event.reason.message); // "Explosion!"
    console.log(rejected === event.promise); // true
  };
  rejected = Promise.reject(new Error("Explosion!"));

四、串联Promise

存在多种方式来将 Promise 串联在一起,以完成更复杂的异步行为。

每次对then()或者catch的调用实际上创建并返回了另一个Promise,

仅当前一个Promise被完成或者拒绝时,后一个Promise才会被决议。

    let p1 = new Promise(function (resolve, reject) {
        console.log("Hi");
        resolve(42);
    });
    p1.then(function (value) {
        console.log(value);
    }).then(function () {
        console.log("Finished!");
    });

Promise链允许你捕获前一个Promise的完成或拒绝处理函数中发生的错误。

    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    p1.then(function(value) {
        throw new Error("Boom!");
    }).catch(function(error) {
        console.log(error.message); // "Boom!"
    });

1.在Promise链中返回值

Promise链的另一个重要方面是能从一个Promise传递数据给下一个Promise的能力。

    let p1 = new Promise(function(resolve, reject) {
        console.log("Start!");
        resolve(42);
    });
    let p2 = p1.then(function(value) {
        console.log(value); // "42"
        return value + 1;
    });
    console.log("break it?");
    p2.then(function(value) {
        console.log(value); // "43"
    });
    /*Start! 
    break it? 
    42 
    43*/

2.在Promise链中返回Promise

    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    p1.then(function(value) {
        // 第一个完成处理函数
        console.log(value); // 42
        return p2;
    }).then(function(value) {
        // 第二个完成处理函数
        console.log(value); // 43
    });

等价于:

    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    let p3 = p1.then(function(value) {
        // 第一个完成处理函数
        console.log(value); // 42
        return p2;
    });
    p3.then(function(value) {
        // 第二个完成处理函数
        console.log(value); // 43
    });    

但是:

    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    let p2 = new Promise(function(resolve, reject) {
        reject(43);
    });
    p1.then(function(value) {
// 第一个完成处理函数
        console.log(value); // 42
        return p2;
    }).then(function(value) {
// 第二个完成处理函数
        console.log(value); // 永不被调用
    });
    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    let p2 = new Promise(function(resolve, reject) {
        reject(43);
    });
    p1.then(function(value) {
// 第一个完成处理函数
        console.log(value); // 42
        return p2;
    }).catch(function(value) {
// 拒绝处理函数
        console.log(value); // 43
    });
    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    p1.then(function(value) {
        console.log(value); // 42
// 创建一个新的 promise
        let p2 = new Promise(function(resolve, reject) {
            resolve(43);
        });
        return p2
    }).then(function(value) {
        console.log(value); // 43
    });

五、响应多个Promise

能监视多个Promise的方法:

1.Promise.all()

1)Promise.all() 方法接收单个可迭代对象(如数组) 作为参数,并返回一个 Promise 。这个
可迭代对象的元素都是 Promise ,只有在它们都完成后,所返回的 Promise 才会被完成

2)若传递给 Promise.all() 的任意 Promise 被拒绝了,那么方法所返回的 Promise 就会立刻被
拒绝,而不必等待其他的 Promise 结束

2.Promise.race()方法

该方法同样接收一个包含 promise 的可迭代类型并返回一个 promise,不过返回的时机是在单个 promise 执行完毕
的那一刻,而不是像 Promise.all() 那样需要等待所有的 promise 都处于 fulfilled 状态。只要有
promise 转变为 fulfilled 状态,那么Promise.race() 就会返回它。

六、Promise的继承

和其他内置类型相似,可以将Promise作为基类,这允许你以内置的Promise为基础对它进行一些改进。

posted @ 2019-05-25 16:53  Shadowplay  阅读(333)  评论(0编辑  收藏  举报