文章目录
1. 回调函数
当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有满足一定条件后该函数才能执行。
回调函数放在另外一个函数(eg:parent)的参数列表中,作为参数传递给parent,然后在parent函数的某个位置执行。
eg:定时器,Ajax中存在回调函数
2. 异步任务
同步任务在主线程上排队,只有前一个任务执行完毕,才能执行下一个任务。
异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。
不阻塞后面任务执行的任务就叫做异步任务。
在es6的Promise出来之前, 我们大多数时候是在使用回调函数(callback)和事件来处理一些异步问题。
-
事件: 某个对象的属性是一个函数, 当发生一件事时, 运行该函数
-
callback回调函数: 运行某个函数以实现某个功能的时候, 传入一个函数作为参数, 当某个特定的条件下的时候, 就会触发该函数
从本质上来说, 事件和回调函数也没有什么太大的区别, 仅仅就是函数放置的位置不太一样而已。
setTimeout(function() {
// 这个函数就是callback回调函数
}, 10)
$ajax({
url: '/api/getUserInfo',
success: function() {
// 成功的回调函数
},
error: function() {
// 失败的回调函数
}
})
var div = document.querySelector('#app');
div.addEventListener('click', function() {
// 点击div以后执行该函数
}, false)
setTimeout(function() {
console.log('执行了回调函数!');
},3000)
console.log('1111');
分析:
按照代码编写的顺序,
应该先输出“执行了回调函数”,
再输出“111”。
但实际执行结果为:
1111
执行了回调函数!
3. 回调地狱
- 回调地狱就是为是实现代码顺序执行而出现的一种操作
- 回调函数中嵌套回调函数的情况就叫做回调地狱。
eg:我要说一句话,语序必须是下面这样的:”武林要以和为贵,要讲武德,不要搞窝里斗。”
setTimeout(function() {
console.log('武林要以和为贵');
setTimeout(function() {
console.log('要讲武德');
setTimeout(function() {
console.log('不要搞窝里斗');
},3000);
},2000)
},1000)
回调地狱的问题:
- 嵌套层次很深,可读性差,难以维护
- 无法正常使用return和throw
- 无法正常检索堆栈信息
- 多个回调之间难以建立联系
4. Promise
4.1 Promise定义
- Promise是异步编程的一种解决方案,比传统的解决方案(函数回调导致回调地狱、事件)更合理,更强大。
- ES6中将Promise写进了语言标准,统一了用法,提供原生的Promise对象,获取异步操作的消息。Promise提供统一的API,各种异步操作可以用同样的方法来处理。
- Promise是一个容器,容器中保存着某个未来才会结束的事件,通常是异步操作。
Promise对象的两个特点:
对象状态不受外界的影响。 Promise对象代表一个异步操作,有以下三个状态:
pending
:挂起状态(或者称之为等待状态), 未决阶段的状态, 代表事情还在未决, 结果还没出来fulfilled
:已处理(或者称之为处理成功), 已决阶段的状态, 代表事情已经产生结果, 而且这个结果是一个可以按照预定逻辑走下去的结果reject
:已拒绝(或者称之为处理失败), 已决阶段的状态, 代表事情已经产生结果, 但是这个结果跟预想的不太一样, 通常为发生错误
只有异步操作的结果,可以决定当前是哪一种状态,任何操作都无法改变这个状态。
拿一个网络请求来说, 请求的过程中为未决阶段: 状态为pedding, 而请求到了数据状态码为OK则是resolved状态, 而请求失败500服务器错误则是rejected状态
一旦状态改变,就不会再变任何时候都可以得到这个结果。 Promise对象的状态改变只有两种可能:
- 从pending变为fulfilled
- 从pending变为rejected
只要这两种情况发生,状态就凝固了,不会再变,一直保持这个结果,这时九成宫为resolved(已定型)。
4.2 Promise基础用法
打印console.dir(Promise)
说明Promise是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。
4.2.1 生成Promise实例
let promise = new Promise(function(resolve,reject){
if() { //异步操作成功
resolve (value)
}else {
reject (error)
}
})
Promise构造函数接受一个函数作为参数,这个函数的两个参数分别是resolve
和reject
。
- resolve,reject是两个函数,是JavaScript引擎提供的,不用自己部署。
- resolve函数的作用: 将Promise对象的状态从pending改为resolved,在异步函数操作成功的时候调用给,并将异步操作的结果作为参数传递出去。
- reject函数的作用: 将Promise对象的状态从pending改为rejected,在异步操作失败的时候调用,并将异步操作报出的错误作为参数传递出去。
- Promise实例生成之后,可以用then方法来分别指定resolved状态和rejected状态的回调函数
/* runAsync返回一个Promise实例,表示一段时间后才会发生的结果 */
function runAsync() {
let p = new Promise((resolve,reject) => {
setTimeout(function() {
console.log('执行完成');
resolve('随便什么数据')
},2000);
})
return p;
}
/* 过了指定ms指定的时间后,Promise实例的状态就会变为resolved */
runAsync();
// 打印结果:
// 执行完成
/* 过了指定ms指定的时间后
Promise实例的状态就会变为resolved,
然后触发then方法指定的回调函数
Promise实例生成之后
可以用then方法来分别指定resolved状态和rejected状态的回调函数
then方法回调函数中的参数是resolve和reject函数残敌出来的参数 */
runAsync().then(function(data){
console.log(data);
})
// 打印结果:
// 执行完成
// 随便什么数据
4.2.2 Promise对象的执行顺序
let promise = new Promise((resolve, reject) => {
console.log('Promise');
resolve();
});
promise.then(() => {
console.log('Resolved');
});
console.log('Hi');
分析:
- 实例化一个Promise对象,调用构造函数,首先执行console.log(‘Promise’);输出“Promise”,resolve()函数将Promise对象的状态从pending改为resolved
- 使用then方法指定resolved状态回调函数
- 输出“Hi”
- 当期脚本所有同步任务执行完成后,执行then方法指定的回调函数,输出“resolved"
4.2.3 Promise的链式编程 then catch方法
Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。
then:接收两个参数, 一个thenable,意为绑定Promise成功的回调 一个catchable,意为Promise失败的回调 注册的两个函数何时执行由Promise的状态决定, 一旦Promise的状态走向已决的resolved状态则thenable函数执行, 走向rejected状态则catchabale执行
const myPromise = new Promise((resolve, reject) => {
// 我在这里直接触发resolve
resolve('我是成功要传递的数据');
})
myPromise.then((data) => {
console.log(data);
}, err => {
console.log(err);
})
catch:接收一个参数, 意为绑定Promise任务失败的回调, 一旦整个Promise的实例走向了rejected, catchable一定会执行
const myPromise2 = new Promise((resolve, reject) => {
reject('我是失败要传递的错误');
})
// then方法的第二个参数可以不传
myPromise2.then(data => {
console.log(data);
})
myPromise2.catch(err => {
console.log(err);
})
无论是then方法还是catch方法结束一个都会返回一个新的Promise实例
4.2.4 Promise.all()
-
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调
-
用Promise.all来执行,all接收一个数组参数,里面的值最终都返回Promise对象。
function runAsync1() {
let p = new Promise((resolve,reject) => {
setTimeout(function() {
console.log('异步任务1执行完成');
resolve('随便什么数据1')
},2000);
})
return p;
}
function runAsync2() {
let p = new Promise((resolve,reject) => {
setTimeout(function() {
console.log('异步任务2执行完成');
resolve('随便什么数据2')
},2000);
})
return p;
}
Promise.all([runAsync1(), runAsync2()])
.then(function(results){
console.log(results);
});
4.2.5 Promise.race()
- Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2,p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
function runAsync1() {
let p = new Promise((resolve,reject) => {
setTimeout(function() {
console.log('异步任务1执行完成');
resolve('随便什么数据1')
},2000);
})
return p;
}
function runAsync2() {
let p = new Promise((resolve,reject) => {
setTimeout(function() {
console.log('异步任务2执行完成');
resolve('随便什么数据2')
},2000);
})
return p;
}
Promise.race([runAsync1(), runAsync2()])
.then(function(results){
console.log(results);
});
4.3 手写一个Promise
4.3.1 Promise对于状态的控制
Promise对于状态控制上的特点:
- Promise是一个构造函数, 且接受一个函数executor作为参数, 他构造出的实例一开始具有两个属性,一个用来表示当前状态,一个用来表示Promise结果 , 即官方所描述的[[PromiseStatus]]和 [[PromiseValue]]
- 写在参数executor中的代码会立即执行, 同时函数参数会被传递两个参数, 一个resolve, 一个reject用于处理不同的情况
- 用户可以在executor中通过执行两个传递进来的参数提前引导Promise走向已决, 执行的方法不同走向的状态也不同,而Promise的状态一旦改变则不可逆转
- 在executor中如果出现错误, 则会将错误抛出, 并且将状态直接抛向已决的rejected状态
- step1
// Promise是一个构造函数,为了更好的封闭作用域,需要用到立即执行函数
const MyPromise = (function() {
// 常量变量定义
const PromiseStatus = Symbol('PromiseStatus'), // 每个实例上的Promise状态
PromiseValue = Symbol('PromiseValue'); // 每个实例上的Promise结果
// Promise状态, pedding, resolved, rejected
const _PEDDING = 'pedding';
const _RESOLVED = 'resolved';
const _REJECTED = 'rejected';
// 将真正的MyPromise构造函数返回出去
return class MyPromise {
// excutor:用户传递进来的函数参数
constructor(excutor){
// 每个Promise实例刚出生的时候状态为pedding, 结果为undefined
this[PromiseStatus] = _PEDDING;
this[PromiseValue] = undefined;
}
}
}());
阶段性实验代码:
const myPrimose = new MyPromise((resolve,reject) => {
})
console.log(myPrimose);
- step 2
const MyPromise = (function() {
const PromiseStatus = Symbol('PromiseStatus'),
PromiseValue = Symbol('PromiseValue');
const _PEDDING = 'pedding';
const _RESOLVED = 'resolved';
const _REJECTED = 'rejected';
// resolve和reject方法, 我们在原型上或者构造函数中是看不到的
// 所以肯定resolve reject方法在闭包中
const resolve = _data => {
}
const reject = _error => {
}
return class MyPromise {
constructor(excutor){
this[PromiseStatus] = _PEDDING;
this[PromiseValue] = undefined;
// excutor在一开始就会被立马执行, 所以势必是在构造函数中走了一次
excutor(resolve,reject);
}
}
}());
阶段性测试:
const myPrimose = new MyPromise((resolve,reject) => {
console.log('我会立即执行吗?');
})
console.log(myPrimose);
结果在意料之中,executor中的代码被成功执行
- step 3
const MyPromise = (function() {
const PromiseStatus = Symbol('PromiseStatus'),
PromiseValue = Symbol('PromiseValue');
const _PEDDING = 'pedding';
const _RESOLVED = 'resolved';
const _REJECTED = 'rejected';
// 定义一个this指向,避免当前作用域下的this混乱
let self = null;
/*
考虑到改变状态这个操作在resolve、reject中只有参数不同,我们可以抽离成一个方法
_status: 新的状态值
_result: 新的value值;
我们知道用户一旦将状态推向已决, 那么PromiseValue就一定要得出一个结果
*/
const changePromiseStatus = (_status,_result) => {
//当前阶段不是已决
if(self[PromiseStatus] !== _PEDDING) return;
self[PromiseStatus] = _status;
self[PromiseValue] = _result;
}
const resolve = _data => {
changePromiseStatus(_RESOLVED,_data);
}
const reject = _error => {
changePromiseStatus(_REJECTED,_error);
}
return class MyPromise {
constructor(excutor){
// this指向self
self = this;
this[PromiseStatus] = _PEDDING;
this[PromiseValue] = undefined;
excutor(resolve,reject);
}
}
}());
阶段性测试:
const myPrimose = new MyPromise((resolve,reject) => {
console.log('我会立即执行吗?');
resolve('hello');
})
console.log(myPrimose);
const mySecPromise = new MyPromise((resolve, reject) => {
console.log('我是第二个Promise实例');
reject('error');
})
console.log(mySecPromise);
测试结果:
- step 4
// 对状态控制的最后一个处理
const MyPromise = (function() {
const PromiseStatus = Symbol('PromiseStatus'),
PromiseValue = Symbol('PromiseValue');
const _PEDDING = 'pedding';
const _RESOLVED = 'resolved';
const _REJECTED = 'rejected';
let self = null;
const changePromiseStatus = (_status,_result) => {
if(self[PromiseStatus] !== _PEDDING) return;
self[PromiseStatus] = _status;
self[PromiseValue] = _result;
}
const resolve = _data => {
changePromiseStatus(_RESOLVED,_data);
}
const reject = _error => {
changePromiseStatus(_REJECTED,_error);
}
return class MyPromise {
constructor(excutor){
self = this;
this[PromiseStatus] = _PEDDING;
this[PromiseValue] = undefined;
// 如果我们在executor中报错, 则会直接触发reject从而进入rejected状态,
// 并将错误信息传递给reject方便后续处理
// 所以我们势必需要try catch捕捉一下错误
try{
excutor(resolve,reject);
}catch(error){
reject(error)
}
}
}
}());
阶段性测试:
const myPrimose = new MyPromise((resolve,reject) => {
console.log('我会立即执行吗?');
resolve('hello');
})
console.log(myPrimose);
const mySecPromise = new MyPromise((resolve, reject) => {
console.log('我是第二个Promise实例');
console.log(abc); // 未声明会报错
})
console.log(mySecPromise);
毫无意外, 结果如下, 如果在executor中报错, 则会直接进入rejected状态
4.3.2 Promise的后续处理(then catch)
- then和catch是属于原型上的方法,
then方法
接收两个参数, 第二个参数可以不传, 这两个参数分别是成功后的回调和失败后的回调,catch方法
接收一个参数, 为失败后的回调。 - Promise串联: 我们知道Promise的then和catch是可以链式调用的, 且下一次Promise的then结果是上一次Promise then或者catch的返回值,如果上一次Promise的执行过程中没有出错, 那么不管结局是resolved或者rejected,新的Promise都会走向resolved, 反之rejected。
const MyPromise = (function() {
const PromiseStatus = Symbol('PromiseStatus'),
PromiseValue = Symbol('PromiseValue');
const _PEDDING = 'pedding';
const _RESOLVED = 'resolved';
const _REJECTED = 'rejected';
// 用来存储Promise.then加入的回调
const fullFilledList = Symbol('fullFilledList');
// 用来存储Promise.catch
const rejectedList = Symbol('rejectedList');
// 这个方法时用来处理用户调用then和catch方法时的处理函数
// _status: 要判定的状态, handler: 当前处理函数, queue: 当前处理函数队列
const settleHandler = (_status, handler, queue) => {
if(self[PromiseStatus] === _status) {
setTimeout(() => handler(self[PromiseValue]), 0);
}
else queue.push(handler);
}
let self = null;
const changePromiseStatus = (_status,_result) => {
if(self[PromiseStatus] !== _PEDDING) return;
self[PromiseStatus] = _status;
self[PromiseValue] = _result;
// 一旦用户更改了状态, 所有在then和catch中对应的方法都要执行
if(self[PromiseStatus] === _RESOLVED) self[fullFilledList].forEach(ele => ele(self[PromiseValue]))
else self[rejectedList].forEach(ele => ele(self[PromiseValue]))
}
const resolve = _data => {
changePromiseStatus(_RESOLVED, _data);
}
const reject = _error => {
changePromiseStatus(_REJECTED, _error);
}
return class MyPromise {
constructor(executor) {
self = this;
this[PromiseStatus] = _PEDDING;
this[PromiseValue] = undefined;
this[fullFilledList] = [];
this[rejectedList] = [];
try {
executor(resolve, reject);
}catch(error) {
reject(error);
}
}
// then 和catch方法是在原型上出现的
// then方法接受一个thenbale: 成功回调函数和一个catchable: 失败回调函数
then = (thenable, catchable) => {
// 我们知道, 如果当前状态已经为已决阶段的两种状态了, 那么回调函数
// 会被立即执行, 否则才会放入相应数组
// 设置相应状态
settleHandler('resolved', thenable, this[fullFilledList])
// 因为catchable很有可能不传递, 所以必须容错
typeof catchable === 'function' && this.catch(catchable);
}
// catch方法只接受一个catchable失败回调函数
catch = (catchable) => {
settleHandler('rejected', catchable, this[rejectedList])
}
}
}())
阶段性测试:
const myPromise = new MyPromise((resolve, reject) => {
console.log('我会立即执行吗?');
reject('hello');
})
myPromise.then((data) => {
console.log(data);
}, (err) => {
console.log(err);
})
console.log(myPromise);
4.3.3 Promise.all()
的实现
Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为resolve或 有 reject 状态出现的时候,它才会去调用 .then 方法,它们是并发执行的。
promise.all()
特点解读:
-
Promise.all()方法可以将多个Promise实例包装成为一个Promise对象§,接收一个数组作为参数,数组中不一定需要都是Promise对象,但一定具有Iterator接口。如果不是,调用Promise.resolve将其转化为Promise对象之后再进行处理。
-
使用Promise.all()生成的Promise对象§的状态由数组中的Promise对象(p1,p2,p3)决定:
- 如果所有的Promise对象(p1,p2,p3)都变成fullfilled状态的话,生成的Promise对象§也会变成fullfilled状态,p1,p2,p3三个Promise对象产生的结果会组成一个数组返回给传递给p的回调函数
- 如果p1,p2,p3中有一个Promise对象变为rejected状态的话,p也会变成rejected状态,第一个被rejected的对象的返回值会传递给p的回调函数。
手写实现自己的Promise.all()
- 版本一
function PromiseAll(arr) {
//PromiseAll的返回值为一个Promise对象
return new Promise((resolve,reject) => {
//传入的PromiseAll必须是一个数组
if(!Array.isArray(arr)){
return reject(new TypeError('arr must be an array.'));
};
let resArr = [];
for(let i in arr) {
(function(i){
Promise.resolve(arr[i]).then(res => {
resArr.push(res);
if(i == arr.length-1){
return resolve(resArr);
}
},err => {
return reject(err);
}).catch(err => {
console.log(err);
})
})(i)
}
})
}
- 版本二
function PromiseAll(arr) {
return new Promise((resolve,reject) => { // 返回一个新的Promise对象
let resArr = []; // 定义一个空数组存放结果
let i = 0;
function handleData(index,data){ // 处理数据函数
resArr[index] = data;
i++;
if(i == arr.length){ //当i等于传递的数组的长度时
resolve(resArr); //执行resolve,并将结果放入
}
}
for(let i=0;i<arr.length;i++) { //循环遍历数组
Promise.resolve(arr[i]).then((data) => {
handleData(i,data); //将结果和索引传入handleData函数
})
}
})
}
测试:
// 测试
const pro1 = new Promise((res,rej) => {
setTimeout(() => {
res('1')
},1000)
})
const pro2 = new Promise((res,rej) => {
setTimeout(() => {
res('2')
},2000)
})
const pro3 = new Promise((res,rej) => {
setTimeout(() => {
res('3')
},3000)
})
const proAll = PromiseAll([pro1,pro2,pro3])
.then(res => {
console.log(res);// 3秒之后打印 ["1", "2", "3"]
}).catch((e) => {
console.log(e);
})
4.3.4 Promise.race()
的实现
function PromiseRace(arr) {
return new Promise((resolve,reject) => {
if(!Array.isArray(arr)){
return reject(new TypeError('arguments must be Array'));
};
for(let i=0;i<arr.length;i++) {
Promise.resolve(arr[i]).then(data => {
resolve(data);
},err => {
reject(err);
})
}
})
}
测试:
// 测试
const pro1 = new Promise((res,rej) => {
setTimeout(() => {
res('1')
},1000)
})
const pro2 = new Promise((res,rej) => {
setTimeout(() => {
res('2')
},2000)
})
const pro3 = new Promise((res,rej) => {
setTimeout(() => {
res('3')
},3000)
})
const proAll = PromiseRace([pro1,pro2,pro3])
.then(res => {
console.log(res); // 输出1
}).catch((e) => {
console.log(e);
})