上一章解释了基础 JavaScript 中的异步编程。本章是通过 Promise 进行异步编程的简介,特别是ECMAScript 6 Promise API。
概述
我们还是首先先看一下怎么创建一个 Promise 吧:
function asyncFunc() {
return new Promise(function (resolve, reject) {
resolve(value); // success
// ...
reject(error); // failure
});
}
我们可以这样来调用它:
asyncFunc()
.then(value => { /* success */ })
.catch(error => { /* failure */ });
处理 Promise 数组
Promise.all ( ) 方法可以让你对 Promise 数组做出反应。
例如,你可以通过 Array 方法 map( ) 创建一个 Promise 数组:
let fileUrls = [
'http://example.com/file1.txt',
'http://example.com/file2.txt'
];
let promisedTexts = fileUrls.map(httpGet);
如果将 Promise.all( ) 应用于该数组,则一旦满足所有 Promise,您将收到一个值数组:
Promise.all(promisedTexts)
// 成功
.then(texts => {
for (let text of texts) {
console.log(text);
}
})
// 失败
.catch(reason => {
// 从 `promisedTexts` 中接收第一个失败信息
});
Promise
Promise 是一种帮助一种特定类型的异步编程的模式:一种异步返回其结果的函数(或方法)。要实现这样的函数,你返回一个 Promise,一个作为结果占位符的对象。函数的调用者使用 Promise 注册回调,以便在计算结果后通知。该函数通过 Promise 发送结果。
让我们看一个第一个例子,给你一个与 Promise 一起工作的样子。
使用 Node.js 样式的回调,异步读取文件如下所示:
fs.readFile('config.json',
function (error, text) {
if (error) {
console.error('Error while reading config file');
} else {
try {
let obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
} catch (e) {
console.error('Invalid JSON in file');
}
}
});
接下来我们使用 Promise 的写法试试看吧:
readFilePromisified('config.json')
.then(function (text) { // (A)
let obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
})
.catch(function (reason) { // (B)
console.error('An error occurred', reason);
});
是不是简洁明了?同时写的时候也省了不少代码量。
这里仍然是回调函数哦,但是它们通过在结果上调用的方法(then( ) 和 catch( ))提供。B 行中的错误回调有两种方法:第一,它是处理错误的单一样式(与前一示例中的if(error)和 try-catch 相比)。其次,你可以从单个位置处理 readFilePromisified( ) 和 A 行中的回调的错误。
看不懂没关系,我们往后一点一点地剖析它。
创建并使用 Promise
在概述中我们写了这么一个函数:
function asyncFunc() {
return new Promise(function (resolve, reject) { // A
resolve(value); // success
// ...
reject(error); // failure
});
}
上述的代码示例中,A 行的 new Promise( )
被称为执行器:
- 如果计算进行顺利,执行器通过 resolve( ) 发送结果。这通常意味着 Promise 操作成功(fulfilled)(也可能不)。
- 如果有错误发生,执行器将通过 rejected( ) 发送结果。这通常意味着 Promise 操作失败(rejected)。
接下来我们要讲述的是重点内容。
一个 Promise 总是处于以下这三种状态之一:
- pending(默认状态):目前,结果还未被计算。
- Fulfilled(成功):结果已经被成功计算了。
- Rejected(失败):在计算期间有错误的响应。
如果 Promise 成功或失败,那么 Promise 将被显示完成(settled)(它表示的计算已经完成)。 Promise 只能完成一次,然后将保持下去。随后企图再去完成(settele)它,将没有任何效果。
以下我们给出图来更好的理解它:
消耗 Promise
当然,使用“消耗”这个词很奇怪。我们将它作为使用吧。也就是我们最多的使用方法,then( )。
为什么 Promise 有用呢?原因就在于它一旦完成了(settled),那么它将不会再改变。也就是说,无论在 Promise 完成前或完成后调用 then( ) 这个方法,都将没有任何效果:
- 如果在完成之前,那么将解决之前的注册的反应,一旦完成,那么就通知它。
- 如果在完成之后,则“立即”接收缓存的结算值。
(1)如果你只关心它成功的情况,那么可以忽略 then( ) 方法的第二个参数:
promise.then(
function (value) { /* fulfillment */ }
);
(2)如果你只对失败的情况感兴趣,那么可以忽略 then( ) 方法的第一个参数:
promise.then(
null,
function (reason) { /* rejection */ }
);
当然,catch( ) 方法也同样有效(这种情况下,这两个方法是等价的):
promise.catch(
function (reason) { /* rejection */ }
);
建议使用 then( ) 专门用于成功的情况,而 catch( ) 用于错误中,因为它很好地标记回调,并且你可以同时处理多个 Promise 的失败情况。
相信大家看到这里也还是一头雾水,这样吧,我们再拿个例子出来:
let p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
});
let p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
});
p2
.then(result => console.log(result))
.catch(error => console.log(error))
上面代码中,p1 是一个 Promise,3 秒之后变为 rejected。p2 的状态在 1 秒之后改变,resolve 方法返回的是p1。此时,由于 p2 返回的是另一个 Promise,所以后面的then语句都变成针对后者(p1)。又过了 2 秒,p1 变为 rejected,导致触发 catch 方法指定的回调函数。
Promise 的内部实现
相信大家可能还是似懂非懂,对于这个 Promise。说实话,它是挺难理解的,特别是对于刚学习这门语言的人来说,因为很少进行异步操作,所以对它的理解不会很深刻。没关系,接下来我们用图的方式给大家讲解内部的原理。
那么我们开始了,等等。。对于以下方法还不知道或不熟悉的同学赶紧先去看下我先前写的文章:Promise 对象,之后再回来学习。
第一步,首先我们先创建一个 Promise 实例。
let dp = new Promise();
dp.resolve('abc');
dp.then(function (value) {
console.log(value); // abc
});
上述的代码我先给解释下吧:首先创建了一个 Promise 实例,命名为 dp;然后我们调用了 resolve( ) 方法,使其在成功时返回内部的值;接着我们调用了 then( ) 方法,使用了第一个参数(第二个参数是可选的),也就是成功时的结果,结果自然就是返回 "abc"。
以下是它的内部解释过程:
上述例子中,我们调用了 then( ) 这个方法,那么这个方法内部返回了什么呢?
如图所示:
调用了 then( ) 方法后,它将返回一个新的 Promise,注意,是新的哦!它内部的值将是成功或失败后的值。
之后我们还可以继续使用 then( ) 对 dp 进行链式调用,那么采用链式的 then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个 Promise 对象(即有异步操作),这时后一个回调函数,就会等待该 Promise 对象的状态发生变化,才会被调用。
如下图所示:
意思大概是这样子。如果真的还不能理解。那真的,暂时不要再看这个啦。先去把我前面讲的更基础的东西理解透彻吧。等着以后再慢慢学习 Promise 也没关系的。
总结
Promise 的学习曲线还算比较陡峭的。因此,看不懂没有关系,其实我也学的不咋地。没有在实战中实践的技术都不能算作技术,因此我们一起加油吧!