JavaScript Promise介绍
前言
众所周知,在JavaScript的世界中,代码都是单线程执行的。由于这个原因,JavaScript中的耗时操作,如网络操作、浏览器事件等,都需要异步执行。这也导致在JavaScript中异步操作是非常频繁且常见的。
异步概念
在执行某些耗时、不会立即返回结果的操作时,不会阻塞后面的操作,一旦该耗时操作完成时,立即通知需要调用其结果的函数来做后续处理。
简单来理解就是:同步按照写的代码顺序执行,异步不按照代码顺序执行
回调函数进行异步操作
和同步操作不同,异步操作是不会立即返回结果的(如发起网络请求,下载文件,操作数据库等)。如果我们后续的函数需要之前返回的结果,又怎样使之前的异步操作在其完成时通知到后续函数来执行呢?
通常,我们可以将这个函数先定义,存储在内存中,将其当做参数传入之前的异步操作函数中,等异步操作结束,就会调用执行这个函数,这个函数就叫做回调函数(callback)。
举个栗子:
// 下载
function download(callback){
// 模拟异步操作
setTimeout(function(){
// 调用回调函数
callback('下载完成');
}, 1000);
}
function callback(value){
// 下载完成的处理
console.log(value);
}
download(callback);
// 这段代码将在1秒后在控制台打印“下载完成”
但假如callback函数同样是个异步函数,且callback里又嵌入了callback呢? 例如需求是等待第一个文件下载完成后,再下载第二个文件,等待第二个文件下载完成后,再下载第三个文件...,这样的话,上面这种方法就不可取了,因为会产生很多的函数嵌套,嵌套太深容易引发回调地狱
Promise进行异步操作
古人云:“君子一诺千金”,这种“承诺将来会执行”的对象,在JavaScript中称为Promise对象。
我们首先看看如何通过Promise改造上面的回调函数异步操作
var download = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
resolve('下载完成');
}, 1000);
})
download.then(value => {
console.log(value);
});
// 这段代码将在1秒后在控制台打印“下载完成”
可以看到代码简洁了一些,当然,不只是如此,它还可以连续调用,例如我们上面所说的回调地狱
问题,通过Promise可以很简洁的实现
var download1 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('1');
resolve('文件1下载完成');
}, 1000);
});
var download2 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('2');
resolve('文件2下载完成');
}, 1000);
});
var download3 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('3');
resolve('文件3下载完成');
}, 1000);
});
download1.then(download2).then(download3);
在download1完成之后会调用download2,download2完成之后会调用download3,实现任务串行,为此,对于那些需要连续执行的异步操作,Promise可以是一种很好的解决办法。
Promise相对于callback,具有更优的代码流,并且具有很好的灵活性。Promise符合自然的事物执行顺序,即先做异步操作,然后再用.then
告知下一步该做什么。而在Callback的用法中,先得知道下一步做什么,然后才能将其作为callback函数传入异步操作函数中。
认识Promise
Promise的创建
Promise创建时,会传给promise一个称为excutor
执行器的函数。这个excutor
我们可以理解为生产者的生产过程函数。这个函数含有两个参数resolve
和reject
,这俩参数也同样是函数,用来传递异步操作的结果
let promise = new Promise(function(resolve, reject) {
// executor
})
有几点值得说一下:
- 在Promise对象创建时,
excutor
会立即执行。 - 在
resolve
和reject
是JS引擎自动创建的函数,我们无需自己创建,只需将其作为参数传入就好。
Promise的状态与执行流程
Promise执行流程图如下:
创建的promise
的内部状态是个对象,初始时为:
{
state, //pending
result, //undefined
}
当Promise中的异步任务执行完,要么产生value
,要么产生error
,此时会立即调用resolve
(当产生value
时)或者调用reject
(当产生error
)时,内部状态也会随之改变,如下图所示:
注意,当excutor
里面即使调用了多个resolve
和reject
,其最终还是只执行一个,其他的都被忽略掉。
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});
多任务并行
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
如果一个页面,需要从多个接口下载文件,但假如这些接口之间没有依从性,此时我们可以让多个任务并行以提升效率。
var download1 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('1');
resolve('文件1下载完成');
}, 500);
});
var download2 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('2');
resolve('文件2下载完成');
}, 800);
});
var download3 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('3');
resolve('文件3下载完成');
}, 1000);
});
Promise.all([download1, download2, download3]).then(function (results) {
console.log(results); // 返回一个数组,包含三个异步任务的执行结果
});
多任务竞争
任务之间是竞争关系,使用Promise也可以很简单的实现。
如果一个页面,需要从多个接口下载文件,但假如只要其中一个任务执行成功获取其结果即可,其它任务便可丢弃。
var download1 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('1');
resolve('文件1下载完成');
}, 500);
});
var download2 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('2');
resolve('文件2下载完成');
}, 800);
});
var download3 = new Promise(function(resolve, reject) {
// 模拟异步操作
setTimeout(function(){
console.log('3');
resolve('文件3下载完成');
}, 1000);
});
Promise.race([download1, download2, download3]).then(function (results) {
console.log(results); // 返回download1的执行结果
});
由于download1执行较快,所以在then
中将获得download1的结果,但是download2与download3任然会继续执行,只是执行的结果将会被丢弃。