在开始写总结笔记之前,先概览下Promise的历史。它作为一种用来处理异步程序的设计模式,在ES6以前就已经被广泛实现了。流行的JS库和框架都各自实现了自己的版本,像jQuery里的Deferred对象,AngularJS。另外甚至还有一些库就是提供promise的实现的,比如Q和Bluebird。
这一章讲的则仅限于ES6里对于promise模式的实现,除了本篇之外,也推荐读一读MDN和Google Developers对于Promise的介绍。
值得特别注意的是,不要混淆这些不同的实现,它们同为promise模式的实现,但是它们互不相干,并且有细微的差异。比如AngularJS里的Promise对象有finally()方法,但是ES6里是没有的。
原文链接:https://pengxiulin.gitbooks.io/es6/content/11-Promises.html
异步处理
JavaScript引擎是单线程,也就是它不能同时执行多段代码。所以需要被执行的代码片段会被记录下来以备完成当前工作后调用执行,引擎通过维护一个队列来管理追踪这些有待执行的代码片段,并且有一个永远运行的事件循环来读取这个队列。
事件模型
略
回调模式
method1(function(err, result) {
if (err) {
throw err;
}
method2(function(err, result) {
if (err) {
throw err;
}
method3(function(err, result) {
if (err) {
throw err;
}
method4(function(err, result) {
if (err) {
throw err;
}
method5(result);
});
});
});
});
Promise基础
promise的意思是承诺,用于表达一个异步操作的执行结果。
// readFile promises to complete at some point in the future
let promise = readFile("example.txt");
比如这段代码里,readFile()不会立刻返回的,因为读文件需要时间,所以,与以前给它一个回调函数或者侦听它的事件那样的做法不同,新的做法里,让它返回一个promise对象。
生命周期
一个承诺有三种状态:
- pending:这是初始的状态,表示这个操作还没有结束;
- fulfilled:已兑现,这就是说该操作已经完成,并且成功得到了预期的结果;
- rejected:被回绝,操作完成了,但是没有成功。
- 操作完成后,承诺可能进入的两个状态统称为:已处理(settled)。从内部实现来讲,promise对象有个内部属性[[PromiseState]],它的值表示着这三个状态。这个属性不可以直接访问,所以没有办法直接判定一个promise对象的状态。只能通过调用then()方法,这个词的意思是:那么,然后。
- then()方法接受两个参数,分别是两个回调函数。第一个函数在该承诺兑现时被调用,并传递给它相应的参数。第二个函数在承诺被拒绝时调用,并传递给它相应参数。实现了then()方法的对象都被统称为“可然后”对象(thenable)。所有的promise对象都是可然后对象,但反过来就不一定成立了。
- then()方法的两个参数都是可有可无的,所以你可以两个都定义,或者只定义其中任意一个:
let promise = readFile("example.txt"); // listen for both fulfillment and rejection promise.then(function(contents) { // fulfillment console.log(contents); }, function(err) { // rejection console.error(err.message); }); // listen for just fulfillment - errors are not reported promise.then(function(contents) { // fulfillment console.log(contents); }); // listen for just rejection - success is not reported promise.then(null, function(err) { // rejection console.error(err.message); });
- 另外promise对象还实现了一个catch()方法,它的效果等同于呼叫then()时只传递给它第二个参数:
promise.catch(function(err) { // rejection console.error(err.message); }); // is the same as: promise.then(null, function(err) { // rejection console.error(err.message); });
- 跟以前的事件处理机制相比,promise的一个独特之处就是,在它已经被处理以后,再呼叫then()传递给它的回调函数依然会被执行,不论是兑现还是拒绝的情况;而且,注意,then()是可以被调用多次的:
let promise = readFile("example.txt"); // original fulfillment handler promise.then(function(contents) { console.log(contents); // now add another promise.then(function(contents) { console.log(contents); }); });
- 每次执行then()和catch()都会创建新的任务(job),但是promise的任务被保存在一个独立的任务队列里(job queue),看起来这个跟引擎的工作方式有关系。作者欲言又止,说深入理解这个机制对于理解promise没有多大帮助。
创建尚未处理的承诺
Vincent:注意前面讲的内容主要都是针对一个promise对象可以做的操作。现在作者返回来讲到如何来创建promise对象。
Vincent:我感觉直接进入作者介绍的任务队列有点突兀,他解释的也不够详细,没有足够的背景介绍。关于这个问题,在我之前写过的其他书的笔记里有提到过,比如同一位作者的另一本书:《High Performance JavaScript》和David Herman的《Effective JavaScript》,见Item 61和Item 65。
现在回到原文这一小节的后半部先:关于任务排程(job scheduling)。
这个机制的特点是,你执行一段代码,虽然代码里有逻辑,可是你告诉JS引擎不要马上执行它们,而是等一下再执行。JS引擎的做法是将这段逻辑——其实就是一个函数——放在一个新为其建立的任务(job)中,并把这个任务放在任务队列里,等待着它的执行。
这个机制最典型的例子就是setTimeout()和setInterval(),在这两个函数里,你传递一个整型参数作为延迟毫秒的数量,而传递的函数里的代码必然会在呼叫setTimeout()的代码全部执行完以后才会在某个时间执行。JS引擎给promise所创建的任务也有着相同的机制,不同之处在于你没有指定时间延迟。比如下面的代码:
let promise = new Promise(function(resolve, reject) {
console.log("Promise");
resolve();
});
promise.then(function() {
console.log("Resolved.");
});
console.log("Hi!");
即使你没有像settimeout()那样指定延迟时间,这段代码的执行结果也永远是Hi在Resolved之前被输出。所以,除了线性地逐行执行代码之外,JavaScript可以定义被搁置的逻辑,等到合适的时机再运行,这就是所谓“异步”的精髓。
现在再回到该小节的前半部,先介绍些术语。在ES6里,通过使用Promise()构造器来创建promise对象,这个构造器接受一个函数作为参数,我们称这个函数为执行器(executor),我个人觉得,这个“执行”的概念,相对于“承诺”而言,它是负责执行这个承诺的实体。执行器会被传入两个参数,resolve()和reject(),它们也都是函数。如果你的承诺执行成功了,你就调用resolve(),并把承诺的值传递给它,否则如果失败了,你就调用reject()。把错误信息传递给它。
在Promise()构造器内包装着你定义的执行器,而在执行器里你通常会调用一个异步的函数,比如Ajax,注意,执行器是在构造器执行时立即被调用的,它不会被放在异步的任务队列里,而当它呼叫resolve()或者reject()时,才会创建异步任务,并放到任务队列里。作者给出了一个Node.js下的例子:
// Node.js example
let fs = require("fs");
function readFile(filename) {
return new Promise(function(resolve, reject) {
// trigger the asynchronous operation
fs.readFile(filename, { encoding: "utf8" }, function(err, contents) {
// check for errors
if (err) {
reject(err);
}
// the read succeeded
resolve(contents);
});
});
}
let promise = readFile("example.txt");
// listen for both fulfillment and rejection
promise.then(function(contents) {
// fulfillment
console.log(contents);
}, function(err) {
// rejection
console.error(err.message);
});
创建已处理的承诺
执行器的错误
全局Promise遭到回绝时的处理
Promise链
多重Promise
囧