Promise从入门到精通
Promise从入门到精通
今天给大家详细讲讲JavaScript中的Promise,保证一听就懂。
一、为什么使用Promise?
JavaScript以单线程方式运行于浏览器之中,而且JavaScript和UI线程也处于同一个线程中。因此对于长时间的耗时操作,将会阻塞UI的响应。为了更好的UI体验,应该尽量的避免在JavaScript中执行耗时较长的操作或者是长时间I/O阻塞的任务。
所以在浏览器中的大多数任务都是异步无阻塞执行的,例如:鼠标点击事件、定时器事件、Ajax回调事件等。当一个事件触发时,它会被放入浏览器的”事件队列“中。JavaScript引擎以单线程的方式一个一个地处理“事件队列”中的事件,当此次处理中再次触发异步事件,它们也会被放入事件队列中,等待下一次的处理。
浏览器事件模型:
基于浏览器的这种事件模型,所以JavaScript一直以回调的方式来处理事件。
当碰到多个连续的JavaScript异步任务时,不可避免地会遇见”callback hell(回调地狱)“,使得这类代码难以维护。
例如:
asyncTask1(data, function (data1) {
asyncTask2(data1, function (data2) {
asyncTask3(data2, function (data3) {
// ......
});
});
});
在上例中,第二个异步任务需要使用到第一个异步任务的返回结果,第三个异步任务需要使用到第二个异步任务的返回结果,导致代码层层缩进,难以阅读。
假如使用Promise,则代码可以变成这样:
asyncTask1(data)
.then(function (data1) {
return asyncTask2(data1);
}).then(function (data2) {
return asyncTask3(data2);
});
可读性就大大提高了。
二、什么是Promise?
Promise,英语意为“承诺”。
- Promise对象封装了一个异步任务。
- 当Promise对象被实例化时,立即执行异步任务。
- Promise对象根据“状态”的变化来决定执行哪个回调函数。
1. Promise对象的状态
Promise对象有三种状态:初始状态、成功状态、失败状态,即pending、fulfilled、rejected。
Promise对象一开始处于pending状态,然后可以根据异步任务执行的成败,切换为fulfilled状态或rejected状态。
2. Promise的构造函数
Promise程序的代码一般是这样的:
var promise = new Promise(function (resolve, reject) {
// 异步任务
if ( /* 异步任务执行成功 */) {
resolve(value);
} else {
reject(error);
}
});
由以上示例代码可以看出,Promise的构造函数需要一个参数,这个参数是一个函数,它封装了异步任务所需执行的代码。另外,这个函数还有两个参数resolve、reject,这两个函数是由JavaScript引擎提供的。
- resolve(value):将Promise对象的状态切换为成功状态,并返回一个指定值。value值可由用户指定。
- reject(error):将Promise对象的状态切换为失败状态,并返回一个指定值。error值可由用户指定。
所以,通常在Promise构造函数的开始部分编写异步任务的代码,然后根据自己的业务逻辑,判断什么情况下异步任务是成功的,成功时调用resolve()将Promise对象的状态切换为成功状态,并触发相应事件,失败时则调用reject()。
3. 注册Promise的回调函数
Promise有好几个方法都可以注册回调函数,回调函数可以响应两个事件:成功、失败。
比较常用的是then()方法。
promise.then(function (value) {
}, function (error) {
});
then()方法可以注册两个回调函数,第一个回调函数用于响应成功事件,其参数value是由resolve(value)传过来的;第二个回调函数用于响应失败事件,其参数error是由reject(error)传过来的。第二个回调函数可以省略。
下面我们通过一个小案例来观察Promise的执行顺序:
let promise = new Promise(function (resolve, rejeact) {
console.log('Promise'); // 1
resolve(); // 切换为成功状态,在事件队列中放入成功事件
});
promise.then(function () {
console.log('Resolved'); // 3
});
for (let i = 0; i < 1000000; i++) { }
console.log('Hi'); // 2
程序的输出结果为:
Promise
Hi
Resolved
首先,new Promise()之后,开始执行Promise内部的代码,因此首先输出“Promise”。之后的resolve()只是把Promise对象的状态设置为成功状态,这时会向事件队列放入一个成功事件,然而我们要记住,异步代码总是在同步代码之后执行的,所以代码继续向下执行,输出“Hi”。promise.then()方法注册了一个回调函数,它在Promise对象的状态切换为成功状态时被调用,因为它是异步代码,所以在最后同步代码都执行完成了,才去处理事件,执行回调函数,输出“Resolved”。
可以使用ES6的箭头函数将上述代码改写为:
let promise = new Promise((resolve, rejeact) => {
console.log('Promise'); // 1
resolve(); // 切换为成功状态,在事件队列中放入成功事件
});
promise.then(() => {
console.log('Resolved');
});
for (let i = 0; i < 1000000; i++) { }
console.log('Hi'); // 2
4. then()方法的返回值
由于我们希望Promise的方法可以链式调用,所以then()方法应该返回一个Promise对象。之前我们有提到过链式调用:
asyncTask1(data)
.then(function (data1) {
return asyncTask2(data1);
}).then(function (data2) {
return asyncTask3(data2);
});
这里是这样规定的,如果你在then()方法中直接返回一个Promise对象,则就以这个对象返回;如果返回的不是一个Promise对象,它会将返回值包装成Promise对象,即新建一个Promise对象,并将你的返回值放在value变量中。
来看一个链式调用的小案例:
var fn = function (num) {
return new Promise(function (resolve, reject) {
if (typeof num === 'number') {
resolve(num);
} else {
reject('TypeError');
}
})
}
fn(2).then(function (num) {
console.log('first: ' + num);
return num + 1;
}).then(function (num) {
console.log('second: ' + num);
return num + 1;
}).then(function (num) {
console.log('third: ' + num);
return num + 1;
});
程序的输出结果为:
first: 2
second: 3
third: 4
大家能根据上面的逻辑,分析一下为什么程序的运行结果是这样的吗?
三、Promise的其它方法
1. catch()方法
通常,我们会先写一长串的then()方法进行链式调用,然后在最后加上一个catch()方法,以捕获Promise的错误。catch()方法的作用是注册失败状态的回调函数。
在链式调用中,只要有一个then()方法导致状态失败,后续的Promise就不会再执行,转而执行catch()方法中的回调函数。
2. all()方法
当我们需要等待多个异步任务处理完成后,再继续向下执行,可以将这几个异步任务归为一组。
Promise.all()方法接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象的状态都变成fulfilled或者rejected的时候,它才会去调用then()方法。
3. race()方法
与Promise.all()方法相似的是,Promise.race()方法也接收一个Promise对象组成的数组作为参数,不同的是,只要数组中的任意一个Promsie对象的状态变成fulfilled或者rejected时,就会调用then()方法。
四、Promise的应用
下面,我们使用Promise来封装Ajax调用。我们编写了一个函数,叫getJSON(url),它可以访问指定url,并返回json格式的数据。
代码如下:
var getJSON = function (url) {
var promise = new Promise((resolve, reject) => {
var client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept', 'application/json');
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
}
});
return promise;
};
getJSON('/posts.json').then(json => {
consoloe.log(json);
}, error => {
console.log('出错了');
});
上一篇: 一天一大 leet (990. 等式方程的可满足性)
下一篇: Promise