关于Promise对象的使用及原理,以及async、await关键字的用法
一、什么是异步编程
1.1、JavaScript 最开始就被设计成了单线程,这也是这门语言的核心特点之一
如果有多个任务就必须排队,一个个依次执行,这种模式的优点就是更安全、更简单,缺点就是遇到遇到耗时任务后面的任务都需要排队等待,可能导致程序出现假死的情况。
为了解决耗时任务的执行,JavaScript 将任务的执行模式分成了两种,分别是同步模式和异步模式。
1.2、同步模式
代码中的任务依次执行,后一个必须等待前一个任务结束才能执行,程序的执行顺序和代码的编写顺序是一致的,这种方式会比较简单,JavaScript 在单线程模式下大多数任务都会按照同步模式执行。
1.3、异步模式
异步模式的 api 不会等待当前任务结束才开始执行下一个任务,对于耗时操作都是开启之后就立即往后执行下一个任务,耗时任务的后续逻辑一般通过回调函数的方式定义,等异步任务执行结束后就会调用回调函数。异步模式对 JavaScript 非常重要,如果没有异步模式单线程的 JavaScript 语言就无法同时处理大量耗时任务,对于开发人员来说,异步模式下的代码执行顺序会出现跳跃,不会像同步代码那样通俗易懂。
异步api有 setTimeout setInterval ajax请求 读取文件的操作等等
console.log('头部');
setTimeout(() => {
console.log('timeout1')
}, 1800);
setTimeout(() => {
console.log('timeout2')
setTimeout(() => {
console.log('inner timeout')
}, 1000)
}, 1000);
console.log('尾部');
1.4、回调函数
这是 JavaScript 异步编程的根本方式,其实所有异步函数的根本都是回调函数,回调函数可以理解为知道要执行什么事情,但是不知道这个事情依赖的任务什么时候完成,所以说最好的办法就是把这些事情的步骤写到一个函数(回调函数)当中,交给任务的执行者,执行者是知道什么时候结束,等结束之后由异步任务的执行者执行。以 ajax 为例,ajax 就是希望拿到数据之后去做一些处理,但是请求什么时候完成不知道,所以得把请求响应之后要执行的任务放到函数中,ajax 执行完成之后会自动执行这些任务,这种由调用者定义,交给执行者执行的函数就被称之为回调函数。具体方法也很简单,就是把函数作为参数传递,只不过这种方式不利于阅读,而且执行顺序也会非常混乱。
1.5、Promise
回调函数是 JavaScript 异步编程的根基,但是直接使用传统回调函数的方式,去完成复杂的异步流程,就无法避免大量的回调函数嵌套,这就会导致常说的回调地狱问题,为了避免回调地狱的问题 CommonJS 社区就提出了 Promise 规范,目的就是为 JavaScript 提供一种更合理、更强大的异步编程方案,后来在 es2015 中被标准化,称为语言规范。
// 回调地狱
fun(function(){
fun1(function(){
fun2(function(){
fun3(function(){
...
})
})
})
})
new Promise()
.then(function(){
})
.then(function(){
})
所谓的 Promise 就是用一个对象表示异步任务结束之后是成功还是失败,就像是内部对外部做出了一个承诺,最开始承诺是待定状态(padding),最终可能成功(Fulfilled),也有可能失败(rejected),不管是达成还是失败,都会有相应的反应 onFulFilled、onRejected,在承诺结束之后都会有相应的任务被执行,而且还有一个明显的特点,就是一旦明确了结果就不可以被改变了。
二、Promise的API及用法
2.1、api
Promise.prototype.thenPromise.prototype.catchPromise.prototype.finallyPromise.resolvePromise.rejectPromise.allPromise.racePromise.allSettled
2.2、实例方法
new Promise((resolve, reject) => {
setTimeout(() => {
// 只会执行第一个resolve或者reject
resolve(1);
// reject(2);
}, 2000);
}).then(value => {
console.log(value);
}, reason => {
console.log(reason);
});
2.3、静态方法
-
2.2.1
Promise.resolve返回一个成功的PromisePromise.resolve返回一个失败的Promise// 传入普通值 Promise.resolve(1).then(value => { console.log(value); }); // 传入成功的Promise对象 Promise.resolve(new Promise((resolve, reject) => { resolve(1); // reject(1); })).then(value => { console.log(value); }); -
2.2.2
Promise.all只返回全部成功的结果,只要有一个失败则调用失败回调var p1 = new Promise(resolve => { resolve(2); }), p2 = new Promise((resolve, reject) => { resolve(3); // 只要有失败的就调用失败回调 // reject(3); }); var list = [1, p1, p2]; Promise.all(list).then(value => { console.log(value); }, reason => { console.log(reason); }); -
2.2.3
Promise.race只返回速度最快的Promise,不管它是否成功var p1 = new Promise(resolve => { setTimeout(() => resolve(1), 3000); }), p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 2000); }), p3 = new Promise((resolve, reject) => { setTimeout(() => resolve(3), 1000); // setTimeout(() => reject(3), 1000); }); var list = [p1, p2, p3]; // 返回最快完成的Promise,不管是否成功 Promise.race(list).then(value => { console.log("成功的" + value); }, reason => { console.log("失败的" + reason); }); -
2.2.4
Promise.allSettled调用成功回调,返回所有Promise的结果,不管里面有没有失败的var p1 = new Promise(resolve => { setTimeout(() => resolve(1), 3000); }), p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 2000); }), p3 = new Promise((resolve, reject) => { setTimeout(() => resolve(3), 1000); // setTimeout(() => reject(3), 1000); }); var list = [p1, p2, p3]; // 只返回所有Promise的结果,不管它是否成功 Promise.allSettled(list).then(value => { console.log(value); });
2.4、错误捕获以及finally
Promise中会自动捕获错误,并调用then方法的失败回调,或者catch,无需使用try-catch捕获
//
new Promise(resolve => {
aaa
resolve(1);
}).then(value => { }, reason => {
console.log(reason);
}).catch(err => {
console.log(err);
});
// finally中不管上面成功还是失败都一定会执行
new Promise((resolve, reject) => {
// resolve(2);
reject(1);
}).then(value => {
console.log(value);
}, reason => {
console.log(reason);
}).finally(() => {
console.log(1213);
});
三、了解Promise的原理
3.1、Promise的微任务队列
3.2、微任务是什么时候创建呢?
当一个padding状态的promise执行了resolve的方法时,我们传then方法的任务不会立即执行,而是把它注册到微任务队列中,
等待同步线程执行完毕,才会去执行微任务中的任务。而后面的then方法必须要等待上一个then方法执行完毕才会被添加到微任务队列中。
3.3、也许会在执行微任务时增加微任务,也就是微任务是按增加的顺序执行
new Promise(resolve => {
console.log(1);
resolve(2);
}).then(value => {
console.log(value);
return 3;
}).then(value => {
console.log(value);
})
new Promise(resolve => {
console.log(4);
resolve(5);
}).then(value => {
console.log(value);
});
3.3、一些常见面试题
- 题目1
setTimeout(()=>{
console.log(1)
},0);
Promise.resolve().then(()=>{
console.log(2)
});
Promise.resolve().then(()=>{
console.log(4)
});
console.log(3);
- 题目2
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve) => {
console.log(2)
resolve()
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
- 题目3
const first = () => (new Promise((resolve, reject) => {
console.log(3) // 同步执行
let p = new Promise((resolve, reject) => {
console.log(7) // 同步执行
setTimeout(() => {
console.log(5) // 放到宏任务 5
resolve(6) // p的状态已经改变了一次 不会在改变了 所以6不会输出
}, 0)
resolve(1) // 微任务 1
})
resolve(2) // 微任务 2
p.then((arg) => {
console.log(arg)
})
}))
first().then((arg) => {
console.log(arg)
})
console.log(4) //同步执行的
- 题目4
setTimeout(() => {
console.log("0")
}, 0)
new Promise((resolve,reject)=>{
console.log("1")
resolve()
}).then(()=>{
console.log("2")
new Promise((resolve,reject)=>{
console.log("3")
resolve()
}).then(()=>{
console.log("4")
}).then(()=>{
console.log("5")
})
}).then(()=>{
console.log("6")
})
new Promise((resolve,reject)=>{
console.log("7")
resolve()
}).then(()=>{
console.log("8")
})
3.2、Promise的运作流程
3.3、实现一个非常简易的myPromise
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise {
constructor(callback) {
this._status = PENDING;
this._value = undefined;
this._fulfilled = [];
callback(this._resolve.bind(this), this._reject.bind(this));
}
_resolve(value) {
function run() {
this._value = value;
this._status = FULFILLED;
let callback = null;
while (callback = this._fulfilled.shift()) {
callback(this._value);
}
}
setTimeout(run.bind(this), 0);
}
_reject() {
}
then(fulfill, reject) {
this._fulfilled.push(fulfill);
}
}
四、async/await 关键字的使用
4.1、我们可以将普通函数声明为一个异步函数
async function fun() {
return 1;
}
// 返回一个Promise对象
console.log(fun());
fun().then(value => {
console.log(value);
});
4.2、使用await来等待一个Promise对象 (await只能在async修饰的函数中使用)
(async function () {
let value = await new Promise(resolve => {
resolve(2);
});
// await下面的语句必须等待await等待的Promise对象执行完毕才会执行
console.log(value);
})();
4.3、使用场景
- 串行调用 (适用于具有依赖性的异步操作)
function fun(v, time) {
console.log("开始");
return new Promise(resolve => {
setTimeout(() => {
resolve(v1);
}, time);
});
}
(async function () {
let val1 = await fun(1, 3000);
console.log(val1);
let val2 = await fun(2, 3000) + val1;
console.log(val2);
})();
- 平行调用 (适用于具有平行关系的异步操作)
function fun(v, time) {
console.log("开始");
return new Promise(resolve => {
setTimeout(() => {
resolve(v1);
}, time);
});
}
(async function () {
let p1 = fun(1, 3000);
let p2 = fun(2, 3000);
let val1 = await p1;
let val2 = await p2;
console.log(val1);
console.log(val2);
})();
4.4、与Promise的区别
- async/await相对于promise来讲,写法更加优雅
- reject状态:
- promise错误只可以通过catch或then来捕捉
- async/await既可以用then又可以用try-catch捕捉
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error');
}, 1000);
});
async function demo(params) {
try {
let result = await p;
} catch (e) {
console.log(e);
}
}
demo();