回调地域:上一个回调函数中继续做事情,而且继续回调(在真实项目的Ajax请求中经常出现回调地域),异步请求,不方便维护
回调地域demo:先获取一个班级,拿到所有学生后获取学生的id,获取到学生的id后再继续操作一下东西
$.ajax({
url: '/student',
method: 'get',
data: {
class: 1
},
success: function (result) {
$.ajax({
url: '/sorce',
method: 'get',
data: {
stuId: result
},
success: function (result) {
let arr = result.map(item => item.id);
$.ajax({
url: '/pass',
method: 'get',
data: {
stuId: arr
},
success: function (result) {
// ...继续做什么操作
}
})
}
})
}
})
Promise的诞生就是为了解决异步请求中的回调地域问题:它是一种设计模式,ES6中提供了一个JS内置类Promise,来实现这种设计模式
function ajax1 () {
return new Promise(resolve => {
$.ajax({
url: '/student',
method: 'get',
data: {
class: 1
},
// success: function (result) {
// resolve(result)
// }
success: resolve // success原本是一条函数,接收resolve函数
})
})
}
function ajax2 (arr) {
return new Promise(resolve => {
$.ajax({
url: '/sorce',
method: 'get',
data: {
stuId: arr
},
success: resolve
})
})
}
function ajax3 (arr) {
return new Promise(resolve => {
$.ajax({
url: '/pass',
method: 'get',
data: {
stuId: arr
},
success: resolve
})
})
}
// 更好的做法:Promise
ajax1.then(result => {
return ajax2(result.map(item => item.id));
}).then(result => {
return ajax3(result);
})
// 目前最好的做法async/await
async function handle() {
// 此处的result就是三次异步请求后获取的信息
let result = await ajax1();
result = await ajax2(result.map(item => item.id));
result = await ajax3(result);
}
Promise是用来管理异步编程的,它本身不是异步的
new Promise的时候会立即把executor函数执行(只不过我们一般会在executor函数中处理一个异步操作)
// 下面结果会先输出1再输出2,证明promise并不是异步的
let p1 = new Promise(() => {
console.log(1)
});
console.log(2);
// 下面输出结果呢?如果答案不对需要去学习eventLoop(也叫事件环)
let p1 = new Promise(() => {
setTimeout(_ => {
console.log(1);
}, 0);
console.log(2)
});
console.log(3);
Promise状态和值
[[PromiseStatus]]
- pending:初始状态,既不是成功,也不是失败状态
- fulfilled(现在改为resolved):意味着操作成功完成
- rejected:意味着操作失败
[[PromiseValue]]
Promise本身还有一个value值,用来记录成功的结果或失败的原因
一般在异步操作结束后,执行resolve/reject函数,执行这两个函数中的一个,都可以修改Promise的[[PromiseStatus]] / [[PromiseValue]]
let p1 = new Promise((resolve, reject) => {
resolve('success');
});
console.log(3);
let p1 = new Promise((resolve, reject) => {
reject('fail');
}); // .catch(e => console.log(e))
console.log(3);
当状态改变后,无法再次扭转状态
let p1 = new Promise((resolve, reject) => {
resolve('succcess');
reject('fail');
});
console.log(3);
Promise.resolve和Promise.reject等价于一个Promise实例去调用executor中的resolve和reject
new Promise((resolve, reject) => {
resolve(100);
// reject(0);
})
Promise.resolve(100); // 等价于上面的写法
Promise.reject(0);
then
promise原型上有一个then方法,用于设置成功或者失败后处理的方法
Promise.prototype.then([resolvedFn], [rejectedFn])
let p1 = new Promise((resolve, reject) => {
setTimeout(_ => {
if(Math.random() < 0.5) {
reject('Fail');
return; // 这段代码不return也行,因为无法二次扭转状态
}
resolve('Success');
}, 300);
});
p1.then(result => {
console.log(result);
}, reason => {
console.log(reason);
})
promise.then()结束会返回一个新的Promise实例(可以实现链式调用,then链)
[[PromiseStatus]]: 'pending'
[[PromiseValue]]: undefined
let p1 = new Promise((resolve, reject) => {
resolve(100);
// reject(0);
});
let p2 = p1.then(result => {
console.log('success: ' + result);
return result + 100;
}, reason => {
console.log('fail: ' + reason);
return -10;
});
let p3 = p2.then(result => {
console.log('success: ' + result);
}, reason => {
console.log('fail: ' + reason);
});
// success: 100, success: 200
总结then的执行结果如下:
- p1这个new Promise出来的实例,成功或者失败,取决于executor函数执行的时候,执行的是
resolve
还是reject
决定的,再或者executor函数执行发生异常错误,也是会把实例状态改为失败 - p2 / p3这种每一次执行then方法返回的新实例的状态,有then中存储的方法执行的结果来决定最后的状态(上一个then中某个方法执行的结果,决定下一个then中哪一个方法会被执行)
- 不论是成功的方法执行,还是失败的方法执行(then中的两个方法),凡是执行抛出了异常,则都会把实例的状态改为失败
- 方法中如果返回一个新的Promise实例,返回这个实例的结果是成功还是失败,也决定了当前实例是成功还是失败
- 剩下的情况基本都是让实例变为成功的状态(方法返回的结果是当前实例的value值;上一个then中返回的结果会传递到下一个then的方法中)
then中也可以只写一个或者不写函数
单纯只有成功:Promise.then(fn)
单纯只有失败:Promise.then(null, fn)
当遇到一个then,要执行成功或者失败的方法,如果此方法并没有在当前then中被定义,则顺延到下一个对应的函数,如下demo:
Promise.reject(10).then(result => {
console.log('success: ' + result);
}).then(null, reason => {
console.log('fail: ' + reason);
});
// fail: 10
也因为这个顺延的特点,所以一般项目中会直接使用catch来捕获错误,错误处理只会在其中必要的时候才会去额外处理。Promise.prototype.catch(fn)
等价于Promise.prototype.then(null, fn)
Promise.reject(10).then(result => {
console.log('success: ' + result);
}).catch(reason => {
console.log('fail: ' + reason);
});
all
Promise.all(arr):返回结果是一个Promise实例(all实例),要求arr数组中的每一项都是一个新的Promise实例,Promise.all是等待数组中的所有实例状态都为成功,才会让all实例
状态为成功。value是一个集合,存储这arr中每一个实例返回的结果;凡是arr中有一个实例状态为失败,all实例
的状态也是失败,返回的结果是按照arr中编写实例的顺序组合在一起的
let p1 = Promise.resolve(1);
let p2 = new Promise(resolve => {
setTimeout(_ => {
resolve(2);
}, 1000);
})
let p3 = Promise.reject(3);
Promise.all([p1, p2, p3]).then(result => {
console.log('success:' + result)
}).catch(reason => {
console.log('fail: ' + reason); // fail: 3
})
Promise.all([p2, p1]).then(result => {
console.log(result); // [2, 1]
console.log('success:' + result); // success: 2,1
})
race
Promise.race(arr):和all不同的地方,race是竞赛,也就是arr中不管哪一个先处理完,处理完的结果作为race实例
的结果(真实项目中基本用不到)
async/await
目前项目一般不会用到promise(除非是小程序项目),因为ES7提供了更方便的语法糖:async/await
async是让一个普通函数返回的结果变为[[PromiseStatus]]为[[PromiseValue]]为return结果的promise实例
async function fn () {
return 10;
}
async function fn1 () {
setTimeout(_ => {
return 20;
})
}
console.log(fn(), fn1());
不过一般项目中不会出现只写async,而是和await配套使用。await会等待当前promise的返回结果,只有结果返回的状态是resolved情况,才会吧返回结果复制给result
await不是同步编程,也是异步的(微任务,同then一样是微任务):当代码执行遇到await,构建一个异步的微任务(等待promise的返回结果,并且await下面的代码也都被列到任务队列中,而本身await是会被立即执行的),如果promise是失败状态,则await不会接收其返回结果,await下面的代码也不会被执行到
let p1 = Promise.resolve(100);
let p2 = new Promise(resolve => {
setTimeout(_ => {
resolve(20);
}, 1000);
})
let p3 = Promise.reject(3);
async function fn () {
console.log(1);
let result = await p1;
console.log(result);
let result2 = await p2;
console.log(result2);
let result3 = await p3;
console.log(result3);
console.log(222);
}
fn();
console.log(2);
// 1, 2, 100, 20,可以看到222已经执行不到了,因为result3的promise没返回resolved,所以下面的代码都没法被执行
检验学习成果:请写出下面代码输出顺序
async function async1 () {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2 () {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise(resolve => {
console.log('promise1');
resolve();
}).then(_ => {
console.log('promise2');
})
console.log('script end');
/*
* 输出结果
* 'script start'
* 'async1 start'
* 'async2'
* 'promise1'
* 'script end'
* 'async1 end'
* 'promise2'
* 'setTimeout'
*/
结合eventLoop,再来一道:
console.log(1);
setTimeout(_ => {console.log(2)}, 1000);
async function fn () {
console.log(3);
setTimeout(_ => { console.log(4) }, 20);
return Promise.reject();
}
async function run () {
console.log(5);
await fn();
console.log(6);
}
run();
for(let i = 0; i < 90000000; i++){} // 越150ms
setTimeout(_ => {
console.log(7);
new Promise(resolve => {
console.log(8);
resolve();
}).then(_ => { console.log(9); });
}, 0);
console.log(10);
// 1 5 3 10 4 7 8 9 2
实现一个promise
promise的特点上面也都介绍了,估计实现的代码网上也是一搜一大把,我这里就直接给出实现代码了(不常用的race
和finally
不实现)
class PromiseModel {
constructor (executor) {
// 每一个Promise实例都有一个状态和结果属性
this['[[promiseStatus]]'] = 'pending';
this['[[promiseValue]]'] = undefined;
// 用于存储基于then指定的成功或者失败的方法
this.resolveArr = [];
this.rejectArr = [];
// 定义 resolve / reject 方法,用来改变Promise实例的状态和结果
let change = (status, value) => {
if(this['[[promiseStatus]]'] !== 'pending') return;
this['[[promiseStatus]]'] = status;
this['[[promiseValue]]'] = value;
// 改变完状态后,把基于then指定的对应方法执行
let fnArr = status === 'resolved' ? this.resolveArr : this.rejectArr;
fnArr.forEach(item => {
if (typeof item !== 'function') return;
item(value);
})
}
// 为了保证执行 resolve / reject 的时候,已经通过then把需要执行的方法弄好了,我们判断处理(没有方法的时候,我们让改变状态的操作延迟进行)
let resolve = result => {
if (this.resolveArr.length) {
change('resolved', result);
return;
}
let delayTimer = setTimeout(() => {
change('resolved', result);
clearTimeout(delayTimer);
}, 0);
}
let reject = reason => {
if (this.rejectArr.length) {
change('rejected', reason);
return;
}
let delayTimer = setTimeout(() => {
change('rejected', reason);
clearTimeout(delayTimer);
}, 0);
}
// 异常捕获
try {
executor(resolve, reject);
} catch (err) {
reject();
throw Error(err);
}
}
then (resolveFn, rejectFn) {
// 如果传递的参数不是函数(Null或者不传递),让成功或者失败顺延
if (typeof resolveFn !== 'function') {
resolveFn = result => {
return result;
};
}
if (typeof rejectFn !== 'function') {
rejectFn = reason => {
return PromiseModel.reject(reason);
};
}
// 每一次执行then都会返回一个新的Promise实例(实现then的链式写法)
return new PromiseModel((resolve, reject) => {
// 只要执行新实例的 executor 函数中的 resolve / reject 就能知道新的实例是成功还是失败的
this.resolveArr.push(result => {
try {
// 不报错,则接受方法的返回结果,会根据结果判断成功还是失败
let x = resolveFn(result);
if (x instanceof PromiseModel) {
x.then(resolve, reject);
return;
}
resolve(x);
} catch (err) {
// 方法执行报错,也代表新实例是失败的
reject(err.message);
}
});
this.rejectArr.push(reason => {
try {
// 不报错,则接受方法的返回结果,会根据结果判断成功还是失败
let x = rejectFn(reason);
if (x instanceof PromiseModel) {
x.then(resolve, reject);
return;
}
resolve(x);
} catch (err) {
// 方法执行报错,也代表新实例是失败的
reject(err.message);
}
})
})
}
// Promise.prototype.catch === Promise.prototype.then(null, fn)
catch (rejectFn) {
return this.then(null, rejectFn);
}
// 静态方法
static resolve (result) {
return new PromiseModel(resolve => {
resolve(result);
});
}
static reject (reason) {
return new PromiseModel((_, reject) => {
reject(reason);
});
}
static all (arr) {
return new PromiseModel((resolve, reject) => {
let index = 0,
results = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if(!(item instanceof PromiseModel)) continue;
item.then(result => {
index++;
results[i] = result;
if (index === arr.length) resolve(results);
}).catch(reason => {
// 只要有一个失败,整体就是失败
reject(reason);
})
}
});
}
}