对于js来说,由于本身就是单线程,在执行耗时操作的时候,比如网络请求等。如果使用同步的话,那么会阻塞整个线程,这样的其实是不合理的。在node中提出了一个很重要的特性,非阻塞式 I/O 的模型、而实现这种模型的基础就是基于事件回调机制。
普通的回调模型
如果使用普通的方式进行回调监听的话,有时候在回调函数中又有回调函数,这样的话会造成回调地狱,对于后期代码的阅读和维护增加很大的难度。
XMLHttpRequest request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.status === 200 && request.readyState === 4) {
// success
document.getElementById('sendBtn').addEventListener('click', function(){
console.log('button is clicked...');
})
}
};
request.open('GET', 'https://www.baidu.com', true);
request.send(null);
Promise
promise其实就是为了解决js中异步回调的问题。避免在函数中继续回调,采用了链式的写法,大大提高可阅读行。
let promise = new Promise((resolve, reject) => {
XMLHttpRequest request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.status === 200 && request.readyState === 4) {
// success
resolve();
}else {
reject();
}
};
request.open('GET', 'https://www.baidu.com', true);
request.send(null);
});
promise.then(function() {
document.getElementById('sendBtn').addEventListener('click', function(){
console.log('button is clicked...');
})
}).catch(function(err){
console.log(err);
});
链式调用
promise的then默认会返回一个新的promise对象,所以在then函数中可以实现链式调用。如果要让p1在p2执行完成之后执行的话,那么可以在then函数中返回p1对象即可。
const p1 = new Promise(function (resolve, reject) {
resolve('p1');
});
const p2 = new Promise(function (resolve, reject) {
resolve('p2');
})
p2.then((data) => {
console.log(data);
return p1;
}).then((data)=>{
console.log(data);
})
对于异常的处理
在promise中抛出异常的话,不会终止程序的运行。如果抛出异常,则相当于执行了reject,那么后面的resolve不会修改promise的状态,此时的promise状态已经是reject,不会再被修改。
let promise = new Promise((resolve, reject) => {
console.log('script start');
throw new Error('error');
resolve('success');
})
promise.then((data)=>{
console.log(data);
}).catch((err)=> {
console.log(err);
})
console.log('script end...');
promise.all理解
promise.all会返回一个promise对象,而最关键的就是在最后一个promise任务的then函数中调用该返回的promise对象的resolve告知所有的任务已经执行完毕。
Promise.all = function (...arg) {
return new Promise((resolve, reject) => {
for (let index=0; index<arg.length; index++) {
(function (i) {
Promise.resolve(arg[i]).then(function (result) {
if (i === arg.length-1) {
resolve(result);
}
}).catch(function (err) {
reject(err);
})
})(index);
}
});
};
let promise1 = new Promise(((resolve, reject) => {
console.log('promise1');
resolve();
}));
let promise2 = new Promise(((resolve, reject) => {
console.log('promise2');
resolve();
}));
Promise.all([promise1, promise2]);
Promise其他的静态函数的理解
- Promise.resolve
如果resolve的参数是一个promise的实例的话,那么会将该对象直接返回。
如果是数字或者字符串类型的话,那么会将该基本类型包装成一个promise的实例,主要是为了能够让其调用then进行相应的回调。可以利用这个特性将一个任务添加到微任务队列中,在同步代码执行完毕之后在执行该任务。
Promise.resolve('1')
// 等价于
new Promise(resolve => resolve('1'))
Promise总结
- 在创建promise实例的时候,传递给构造函数的方法中的代码是会立即执行的。遇到resolve或者reject的话,则会将其放置到微任务队列中,继续向下执行,在同步代码执行完毕之后,任务队列会从微任务队列中取出resolve或者reject,然后执行相应的then方法。resolve,reject并不会阻塞后面的同步代码。
new Promise(resolve => {
console.log('main thread'); // 立即执行
resolve();// 放入到微任务队列中,等同步代码执行完毕之后在执行对应的then
}).then(() => {
console.log('2...');
})
console.log('1....');
// 执行结果:
// main thread
// 1....
// 2...
async、await讲解
虽然promise利用了resolve、reject解决了回调函数多次嵌套的问题,但是async、await直接将异步回调采用了同步代码的写法,写法上更贴近同步代码的写法。 下面是基本的用法,其实也是对promise进行一层封装。函数返回一个promise对象给await,await会将resolve中的结果进行返回,达到了将异步代码使用同步的写法。
function request() {
return new Promise((resolve, reject) => {
resolve('resolve data....');
});
}
async function test() {
let data = await request();
console.log(data);
}
test();
async的作用
从下面的代码可以看出,对于async函数,函数返回的是一个promise对象,除了这个其他跟普通的函数其实没什么区别。使用async只是标示该方法中有await。
let fn1 = async function() {
return 1;
}
console.log(fn1());
let fn2 = function fn() {
return 1
}
console.log(fn2());
fn1执行结果
Promise {<resolved>: 1}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: 1
fn2执行结果
1
await的作用
await主要是等待右边的表达式的执行结果。对于表达式的结果有两种情况。
- 结果是一个promise对象。在执行完右边的表达式之后,await会让出线程,await后面的代码则会被阻塞,正是这个原因,才能实现将异步的代码使用同步的写法。让出线程后会执行脚本中的其他同步代码,等同步执行完毕之后,再回到await,此时还不能继续执行,因为此时的返回值是promise对象,所以必须等到该promise对象的状态变为resolve或者reject的时候,await才会返回相应的值,然后继续执行后面的代码。
- 如果返回值不是一个promise对象。此时await也是让出线程,因为只有这样才不会执行await后面的代码,继续执行其他的同步代码,完毕之后回到await,因为此时不是promise对象,相当于是Promise.resolve(obj),那么await则会返回相应的值,然后继续执行后续的代码。
优先级比较
- promise
当一个promise对象有多个then回调的话,实际上每一次的then函数都会返回一个新的promise的实例,已经不在是原来的promise实例。所以也就是为什么先打印2在打印3。创建promise1,打印’createa promise obj 1…’,此时执行resolve,则会将任务放置到微任务队列中,线程继续向下执行。创建promise2,打印’createa promise obj 2…’,执行resolve,同样将任务放置到微任务队列中。到此同步代码执行完毕。接下来执行微任务队列中的任务,第一个promise1的resolve任务,所以会调用对应的then方法,打印’1…’,由于then函数会返回一个promise对象,可以理解成Promise.resolve().then(()=>{console.log(‘3…’)}),当前的promise对象的状态只是resolve,但是还是不会执行then方法,只会将其放置到微任务队列中。然后又从微任务队列中取出promise2的resolve,此时会打印’2…’,最后就是刚才的’3…‘对应的promise对象的resolve,取出该任务之后打印’3…’,完毕。这里其实也可以看成是Promise.resolve的一个应用。
let promise1 = new Promise(resolve => {
console.log('createa promise obj 1...');
resolve();
}).then(() => {
console.log('1...');
}).then(() => {
console.log('3...');
})
let promise2 = new Promise(resolve => {
console.log('createa promise obj 2...');
resolve();
}).then(()=>{
console.log('2...');
});
// 执行结果:
// createa promise obj 1...
// createa promise obj 2...
// 1...
// 2...
// 3...