文章内容输出来源:拉勾大前端高薪训练营
1、Promise 概述 和 基本用法
为了避免回调地狱的问题(大量回调函数的嵌套),commonJS社区提出了Promise的规范,目的为异步编程题提供一种更合理,更强大的统一解决方案,在ES2015中被标准化,成为语言规范
定义
promise 就是一个对象,表示一个异步任务最终结束过后是成功还是失败,就像是内部对外界做的一个承诺,明确结果之后不能更改。
注意
即便 promise 中没有任何的异步操作,then方法中指定的回调函数,仍然会进入到回调队列中排队,也就是必须要等到同步代码执行完了,这里才会执行。
const promise = new Promise(function( resolve, reject){
//这里兑现“承诺”
resolve(100); //承诺达成
//reject(new Error('promise rejected')); //承诺失败
});
promise.then(function(value){
console.log(value);
}, function(value){
console.log(value);
})
console.log('end');
//执行结果:end、100
2、Promise 常见误区
嵌套使用的方式是使用 Promise 最常见的错误
要借助于 Promise 对象中 then 方法链式调用的特点,尽可能的保证异步任务扁平化
3、Promise 链式调用
then 方法的作用就是为 Promise 对象添加状态明确后的回调函数
- 它的第一个参数是 onFulfilled 回调,也就是成功之后的回调;
- 它的第二个参数是 onRejected 回调,也就是失败之后的回调,可以省略
- 最大的特点是内部也会返回一个 Promise 对象。
目的是实现一个 promise 的链条,也就是一个承诺结束之后,再返回一个新的承诺,每个承诺都可以负责一个异步任务,相互之间没有什么影响。
特点
1、Promise 对象的 then 方法会返回一个全新的Promise 对象
2、后面 then 方法就是在为 上一个 then 返回的 Promise 注册回调
3、前面 then 方法中回调函数的返回值 会作为后面 then 方法回调的参数
4、如果回调中返回的是Promise, 那后面的 then 方法的回调会等待它的结束
每一个 then方法 它都是为上一个 then 返回的 promise 对象添加 状态明确之后的回调,这些promise 依次执行。
- 也可以 在 then 中 手动返回一个 promise 对象,那么下一个then 就是为这个对象添加 状态明确之后的回调
- 如果 回调 返回的是一个普通的值,这个值 就会 当成 当前这个then 方法 返回的promise 中的值,那么下一个then 方法中接收到的回调的参数就是这个值。
- 如果 回调中没有返回值 任何值,默认就会返回 undefined。
4、Promise 异常处理
- 如果promise 执行中出现异常或者失败,then 中的 onRejected 都会执行;
- promise中的异常会往后传递,catch 方法可以捕获链式中所有的异常
- then 中只能捕获到上一个 promise 的异常
promise 的 catch 方法注册onRejected 回调,相当于 then 方法的别名,第一个参数是 undefined;
对于链式调用,建议使用分开捕获异常回调,因为promise链式回调中 异常会向后传递。
//Promise 方式的ajax
function ajax(url){
return new Promise(function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function (){ //请求完成 也就是 readyStatus = 4 才会执行
if(this.status == 200){
resolve(this.response);
}else{
reject(new Error(this.statusText));
}
}
xhr.send();
})
}
ajax('/api/users.json').then(function(res){
console.log(res);
return ajax('/api/urls.json')
},function(error){
console.log(error);
})
.then(function(value){
console.log(111);
console.log(value);
// return ajax('/kdkdk')
})
.then(function(value){
console.log(2222);
console.log(value);
return 'foo';
})
.then(function(value){
console.log(444);
console.log(value);
})
.catch(function(error){
console.log(error);
});
执行结果
五、Promise 静态方法
1、Promise.resolve() :快速把一个值转化成一个 Promise 对象
- 这个方法 如果接收到的是另一个promise 对象,这个 promise 对象会被原样返回;
- 如果这个方法接收一个有 then 方法相同的 方法 的 对象,也可以返回promise 对象
Promise.resolve('200')
.then(function(value){
console.log(value)
})
//等价于
new Promise(function(resolve, reject){
resolve('200')
}).then(function(value){
console.log(value);
});
//结果: 200 200
var promise1 = ajax('/api/users.json');
var promise2 = Promise.resolve(promise1);
console.log(promise1 === promise2);
//结果: true
Promise.resolve({
then: function(onFulFiled, onRejected){
onFulFiled('foo');
}
})
.then(function(value){
console.log(value);
})
//结果: foo
2、Promise.reject(): 快速创建一个失败的Promise 对象
Promise.reject('anything')
.catch(function(error){
console.log(error)
})
//结果:anything
六、Promise 并行执行
Promise.all() 和 Promise.race() 方法
1、Promise.all(): 可以将多个promise 合并成一个 Promise 统一去管理,返回的 value 是一个数组
- 它接收 一个数组,数组由一个一个的promise 对象组成,可以看成是一个一个 的任务,
- 返回一个全新的 Promise 对象,数组中所有的 Promise 任务都结束了之后,这个 Promise 任务才完成,只要数组中有一个任务失败,它就也失败
- 成功返回的结果是一个数组,包含每个异步任务执行过后的结果。
Promise.all([
ajax('/api/users.json'),
ajax('/api/urls.json')
]).then(function(values){
console.log(values)
})
//综合使用串联和并行执行 的这两种方式
ajax('/api/urls.json')
.then(value => {
const urls = Object.values(value);
const tasks = urls.map( url => ajax(url))
return Promise.all(tasks)
}).then( values =>{
console.log(values)
})
执行结果
2、Promise.race(): 可以将多个promise 合并成一个 全新的 Promise 对象,只会等待第一个结束的任务
- 它跟着第一个结束的任务,一起结束,只要有任何一个promise 异步任务完成了,它就也完成
const request = ajax('/api/users.json');
const timeout = new Promise( (resolve, reject) => {
setTimeout(() => reject(new Error('timeout')), 500)
});
Promise.race([
request,
timeout
])
.then(value => {
console.log(value)
})
.catch( error => {
console.log(error);
})
通过修改网络限速的方式查看到效果:
七、Promise 执行时序 / 宏任务vs微任务
promise 要等到同步代码执行完了,promise 回调 才会执行
宏任务 和 微任务
- 回调队列中的任务称之为 “宏任务”,宏任务执行过程中可以临时加上一些额外的需求
-- 可以选择作为一个新的宏任务进到队列中 进行排队,
-- 也可以作为当前任务的微任务,直接在当前的任务结束之后立即执行 - Promise 的回调会作为微任务执行
- setTimeout 回调会作为宏任务进入到 宏任务进到队列的末尾
console.log('global start');
setTimeout(() => {
console.log('setTimeout');
}, 0)
Promise.resolve()
.then(() => {
console.log('promise1')
})
.then(() => {
console.log('promise2')
})
console.log('global end');
// 执行结果: global start、global end、promise1、promise2、setTimeout
微任务的好处:
- 减少操作时用户感知到的延迟
- 确保任务顺序的一致性,即便当结果或数据时同步可用的
- 批量操作的优化
微任务可以提高整体的响应能力
目前绝大多数异步调用都是作为宏任务执行
Promise对象、MutationObserver对象、queueMicrotask(微任务队列) 和 node 中的 process.nextTick 他们都是作为微任务,直接在本轮任务的末尾就执行了
//创建微任务
queueMicrotask(() => {
console.log('微任务')
})
批量操作优化:下面的微任务(queueMicrotask中的回调)只执行了一次就可以发送三条消息
const msgQueue = [];
function sendMessage(msg){
msgQueue.push(msg);
if(msgQueue.length == 1){
queueMicrotask(() => {
const json = JSON.stringify(msgQueue);
msgQueue.length = 0;
// fetch('url-of-receiver', json)
console.log(json)
})
}
}
sendMessage('哈哈哈');
sendMessage('你好呀');
sendMessage('呵呵哒');
八、Promise 源码相关的说明
1、Promise 就是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
2、Promise 中有三种状态,分别为: 成功(fulfilled)、失败(rejected)、等待(pending)
状态改变:pending -> fulfilled 或者 pending-> rejected, 一旦状态确定就不可更改
3、resolve 和 reject 函数是用来更改状态的:resolve: fulfilled、reject: rejected
4、then 方法的内部做的事情就是判断状态:如果状态是成功,调用成功的回调函数,如果是状态失败,调用失败的回调函数。then 方法是被定义在原型对象中的
5、then成功回调有一个参数,表示成功之后的值,失败回调有一个参数,表示失败之后的原因
6、then方法可以不传递参数,这个Promise 的状态会依次向后传递,直到传递给有回调函数的 then 方法。
7、resolve 把给定的值转化成 promise 对象
8、all 方法: 解决异步并发问题。按照异步代码调用的顺序得到异步代码执行的结果;