promise
1. 引入promise
// 向服务器发送请求
// /student?class=1 获取班级学员信息
// /score?stuId=[1,2,3] 返回学员的分数信息
// /jige?score=90 确定是否及格
$.ajax({
url: '/student',
method: 'get',
data: {
class: 1
},
success: function (result) {
// result=>学生信息
$.ajax({
url: '/score',
method: 'get',
data: {
stuId: result.map(item => item.id)
},
success: function (result) {
// result=>学员的分数信息
$.ajax({
//...
});
}
});
}
});
-
回调地狱: 上一个回调函数中继续做事情,而且继续回调(在真实项目的AJAX请求中经常出现回调地狱)
=>异步请求、不方便代码的维护 -
Promise的诞生就是为了解决异步请求中的回调地狱问题:它是一种设计模式,ES6中提供了一个JS内置类Promise,来实现这种设计模式。
function ajax1() {
return new Promise(resolve => {
$.ajax({
url: '/student',
method: 'get',
data: {
class: 1
},
success: resolve
});
});
}
function ajax2(arr) {
return new Promise(resolve => {
$.ajax({
url: '/score',
method: 'get',
data: {
stuId: arr
},
success: resolve
});
});
}
function ajax3() {
return new Promise(resolve => {
$.ajax({
url: '/jige',
// ...
success: resolve
});
});
}
ajax1().then(result => {
return ajax2(result.map(item => item.id));
}).then(result => {
return ajax3();
}).then(result => {
});
- 改用async的方式
async function handle() {
let result = await ajax1();
result = await ajax2(result.map(item => item.id));
result = await ajax3();
// 此处的result就是三次异步请求后获取的信息
}
handle();
2. promise概念
1. executor
PROMISE是用来管理异步编程的,它本身不是异步的:new Promise的时候会立即把executor函数执行(只不过我们一般会在executor函数中处理一个异步操作)
new Promise([executor]) // [executor]执行函数是必须传递的
let p1 = new Promise(() => {
console.log(1); //=>1
});
console.log(2); //=>2
// 先1 后2.立即执行1
let p1 = new Promise(() => {
setTimeout(_ => {
console.log(1);
}, 1000);
console.log(2);
});
console.log(3);
// 先执行promise函数,输出2,然后3,1s后定时器输出1
let p1 = new Promise(() => {
setTimeout(_ => {
console.log(1);
}, 1000);
console.log(2);
});
console.log(3);
// 2 3 1
2. promise的三个状态和value值
- 三个状态
pending 初始状态
fulfilled 代表操作成功(resolved)
rejected 代表当前操作失败
- value值
PROMISE本身有一个VALUE值,用来记录成功的结果(或者是失败的原因的)
=>[[PromiseValue]]
let p1 = new Promise((resolve, reject) => {
setTimeout(_ => {
// 一般会在异步操作结束后,执行resolve/reject函数,执行这两个函数中的一个,都可以修改Promise的[[PromiseStatus]]/[[PromiseValue]]
// 一旦状态被改变,在执行resolve、reject就没有用了
resolve('ok');
reject('no');
}, 1000);
});
3. promise的then方法
new Promise的时候先执行executor函数,在这里开启了一个异步操作的任务(此时不等:把其放入到EventQuque任务队列中),继续执行。p1.then基于THEN方法,存储起来两个函数(此时这两个函数还没有执行);当executor函数中的异步操作结束了,基于resolve/reject控制Promise状态,从而决定执行then存储的函数中的某一个。
let p1 = new Promise((resolve, reject) => {
setTimeout(_ => {
let ran = Math.random();
console.log(ran);
if (ran < 0.5) {
reject('NO!');
return;
}
resolve('OK!');
}, 1000);
});
// THEN:设置成功或者失败后处理的方法
// Promise.prototype.then([resolvedFn],[rejectedFn])
p1.then(result => {
console.log(`成功:` + result);
}, reason => {
console.log(`失败:` + reason);
});
练习: 1.
let p1 = new Promise((resolve, reject) => {
console.log(1)
resolve(100);
console.log(2)
});
p1.then(result => {
console.log(`成功:` + result);
}, reason => {
console.log(`失败:` + reason);
});
console.log(3);
// 1 2 3 成功:100
let p1 = new Promise((resolve, reject) => {
// resolve/reject 的执行,不论是否放到一个异步操作中,
// 都需要等待then先执行完,把方法存储好
//,才会在更改状态后执行then中对应的方法.
// =>此处是一个异步操作(所以很多人说PROMISE是异步的),而且是微任务操作
resolve(100);
});
p1.then(result => {
console.log(`成功:` + result);
}, reason => {
console.log(`失败:` + reason);
});
console.log(3);
// 3 再输出 100
- 创建一个状态为成功/失败的
PROMISE实例
let p1 = new Promise((resolve, reject) => {
resolve(100);
})
//创建一个状态为成功PROMISE实例 , 这样的写法也可以被这种写法代替
//=> Promise.resolve(100)
// => Promise.reject(0)
4. THEN链
let p1 = new Promise((resolve, reject) => {
resolve(100);
}) //创建一个状态为成功PROMISE实例 ;
let p2 = p1.then(result => {
console.log('成功:' + result);
return result + 100;
}, reason => {
console.log('失败:' + reason);
return reason - 100;
});
let p3 = p2.then(result => {
console.log('成功:' + result);
}, reason => {
console.log('失败:' + reason);
});
//成功200 成功100
THEN方法结束都会返回一个新的Promise实例(THEN链)
[[PromiseStatus]]:'pending'
[[PromiseValue]]:undefined
p1这个new Promise出来的实例,成功或者失败,取决于executor函数执行的时候,执行的是resolve还是reject决定的,再或者executor函数执行发生异常错误,也是会把实例状态改为失败的p2/p3这种每一次执行then返回的新实例的状态,由then中存储的方法执行的结果来决定最后的状态(上一个THEN中某个方法执行的结果,决定下一个then中哪一个方法会被执行)
- 不论是成功的方法执行,还是失败的方法执行(THEN中的两个方法),凡是执行抛出了异常,则都会把实例的状态改为
失败 - 方法中如果返回一个
新的PROMISE实例,返回这个实例的结果是成功还是失败,也决定了当前实例是成功还是失败 - 剩下的情况基本上都是让实例变为成功的状态 (方法返回的结果是当前实例的value值:上一个then中方法返回的结果会传递到下一个then的方法中)
- 练习
Promise.resolve(1)
.then(result => {
console.log(`成功:${result}`); // 成功1
return result * 10;
}, reason => {
console.log(`失败:${reason}`);
}).then(result => {
console.log(`成功:${result}`); // 成功10
}, reason => {
console.log(`失败:${reason}`);
});
new Promise((resolve)=>{
resolve(a) // => 报错,状态变为失败
})
.then(result => {
console.log(`成功:${result}`);
return result * 10;
}, reason => {
console.log(`失败:${reason}`); //=> 失败 a is not defined
})
//上面执行的是是成功的,所以状态是成功的。只不过没有返回结果
.then(result => {
console.log(`成功:${result}`); //=》 成功 undefined
}, reason => {
console.log(`失败:${reason}`);
});
Promise.resolve(10)
.then(result => {
console.log(`成功:${result}`);
return Promise.reject(result * 10);
}, reason => {
console.log(`失败:${reason}`);
}).then(result => {
console.log(`成功:${result}`);
}, reason => {
console.log(`失败:${reason}`);
});
// 成功 10 失败 100
- TEHN中也可以只写一个或者不写函数
.then(fn)
.then(null,fn)
遇到一个THEN,要执行成功或者失败的方法,如果此方法并没有在当前THEN中被定义,则顺延到下一个对应的函数
Promise.reject(10).then(result => {
console.log(`成功:${result}`);
return result * 10;
}).then(null, reason => {
console.log(`失败:${reason}`); // 失败 10
});
5. catch
Promise.prototype.catch(fn) ===> .then(null,fn)
Promise.resolve(10).then(result => {
console(a);//=>报错了,让状态变为失败
}).catch(reason => {
console.log(`失败:${reason}`); // 失败 a is not defined
});
6. Promise.all
Promise.all([promise1, promise2]).then(success1, fail1)
- 返回的结果是一个
PROMISE实例(ALL实例),要求ARR数组中的每一项都是一个新的PROMIE实例。 PROMISE.ALL是等待所有数组中的实例状态都为成功才会让“ALL实例”状态为成功,VALUE是一个集合,存储着ARR中每一个实例返回的结果。- 凡是ARR中有一个实例状态为失败,“ALL实例”的状态也是失败。
let p1 = Promise.resolve(1);
let p2 = new Promise(resolve => {
setTimeout(_ => {
resolve(2);
}, 1000);
});
let p3 = Promise.reject(3);
Promise.all([p2, p1,p3]).then(result => { // 有p3就失败,没有p3成功。
// 返回的结果是按照ARR中编写实例的顺序组合在一起的,不是谁先到先执行谁
// [2,1]
console.log(`成功:${result}`); //成功 [2 1]
}).catch(reason => {
console.log(`失败:${reason}`);
});
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
7. Promise.race
Promise.race([promise1, promise2]).then(success1, fail1)
- promise1和promise2
只要有一个成功就会调用success1; - promise1和promise2
只要有一个失败就会调用fail1; - 总之,谁第一个成功或失败,就认为是race的成功或失败。
- 和ALL不同的地方,
RACE是赛跑,也就是ARR中不管哪一个先处理完,处理完的结果作为“RACE实例”的结果。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two"
8. ES7中提供了PROMISE操作的语法糖:async / await
- async
async是让一个普通函数返回的结果变为STATUS=RESOLVED并且VALUE=RETRN结构的PROMISE实例async最主要的作用是配合await使用的,因为一旦在函数中使用await,那么当前的函数必须用async修饰
async function fn() {
return 10;
}
console.log(fn());
await会等待当前promise的返回结果,只有返回的状态是RESOLVED情况,才会把返回结果赋值给RESULT。await不是同步编程,是异步编程(微任务):当代码执行到此行(先把此行),构建一个异步的微任务(等待PROMISE返回结果,并且AWAIT下面的代码也都被列到任务队列中)。 一段代码:
let p1 = Promise.resolve(100);
async function fn() {
console.log(1);
let result = await p1;
console.log(result);
}
fn();
console.log(2);
输出: 1 2 100
let p1 = Promise.resolve(100);
let p2 = new Promise(resolve => {
setTimeout(_ => {
resolve(200);
}, 1000);
});
let p3 = Promise.reject(3);
async function fn() {
console.log(1);
// 有2 个await
let result = await p2;
console.log(result); // 200
let AA = await p1;
console.log(AA); // 100
}
fn();
console.log(2);
输出: 1 2 200 100
4. 如果promise是失败状态,则await不会接收其返回结果,await下面的代码也不会在继续执行(await只能处理PROMISE为成功状态的时候)
let p3 = Promise.reject(3);
async function fn() {
let reason = await p3;
console.log(reason);
}
fn();
9. 题
1.
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
- 答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
undefined
setTimeout
- 解析:
- 主线程自上而下执行,创建async1函数,async2函数。
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
console.log("script start") => script start
2. 遇到setTimout,设置定时器1。属于宏任务,0ms => 执行任务(设置定时器是同步任务,而多少秒后执行,属于异步宏任务)
setTimeout(function () {
console.log('setTimeout');
}, 0)
- 执行async1(), console.log(" async1 start ") =>
async1 start - 遇到
await async2();
立即执行async2()(即await后面的代码),但这行下面的代码需要等返回正确的promise结果再执行,也就是说这行下面的一行或几行属于异步任务(微任务),我们将他命名为微任务2。=> async2
- 继续执行主线程里的代码,遇到
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
new Promise是同步任务,立即执行executor。所以输出promise1。 resolve()是异步操作(微任务),用.then()存储响应的方法到异步队列中,将他命名为微任务3。=> promise1
- 继续执行主线程中的任务
console.log('script end');
输出script end => script end
- 此时主线程中的同步任务都执行完了,开始执行事件队列中的异步任务。
先微任务,再宏任务。- 此时任务队列中微任务有微任务2,3.宏任务有定时器1。
- 执行微任务2:=>
async1 end - 执行微任务3: =>
promise 2 - 执行宏任务(定时器1):=>
setTimeout
2.题
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();
// 需要执行150MS左右
for (let i = 0; i < 90000000; i++) {}
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
解析:
- console.log(1) =>
1 - 设置定时器1,1000ms后,输出 =>
2.
setTimeout(_ => { console.log(2); }, 1000);
- 创建fn()函数,只创建
async function fn() {
console.log(3);
setTimeout(_ => { console.log(4); }, 20);
return Promise.reject();
}
- 创建run()函数
async function run() {
console.log(5);
await fn();
console.log(6);
}
- 执行run()
async function run() {
console.log(5);
await fn();
console.log(6);
}
- 先输出5 =>
5 - 到
await fn()立即执行fn(),而它下面的异步代码,需要等fn返回成功态才会执行。 - 设置下面的代码为微任务2(即fn执行的结果是成功后,输出
6)。
- 执行fn()
async function fn() {
console.log(3);
setTimeout(_ => { console.log(4); }, 20);
return Promise.reject();
}
- 先输出3 =>
3 - 设置定时器。命名为定时器3。20ms输出
4。 - 返回一个失败状态
- 由于失败了,所以微任务2不会执行。
- 遇到循环
for (let i = 0; i < 90000000; i++) {}
需要执行150MS左右,也就是说要等待150ms 8. 设置定时器4
setTimeout(_ => {
console.log(7);
new Promise(resolve => {
console.log(8);
resolve();
}).then(_ => { console.log(9); });
}, 0);
- 输出10 =>
10 - 此时主线程任务完成,开始执行任务队列。
先微任务,后宏任务。微任务中没有需要执行的。所以执行宏任务。 - 此时定时器3已经完成计时,可以执行=>
4 - 定时器4执行,先输出7 =>
7 - 定时器4中
new promise
- executor先执行,输出8 =>
8。 - resolve()是一个微任务,放入队列。命名为微任务5(会把后期then方法中存储的第一个方法执行)
队列中出现了微任务,不管此时宏任务时候已经完成,都要先执行微任务。输出微任务5 =>9- 执行宏任务1 =>
2