在js中任务的执行模型有两种:同步模式
和异步模式
。
-
同步模式:后一个任务B等待前一个任务A结束后,再执行。任务的执行顺序和任务的排序顺序是一致的。
-
异步模式:每一个任务有一个或多个回调函数,前一个任务A结束后,不是执行后一个任务B,而是执行任务A的回调函数。而后一个任务B是不等任务A结束就执行。任务的执行顺序,与任务的排序顺序不一致。
异步在实现上,依赖一些特殊的语法规则。从整体上来说,异步方案经历了如下的四个进化阶段:回调函数 —> Promise —> Generator —> async/await。其中 Promise、Generator 和 async/await 都是在 ES2015 之后,慢慢发展起来的、具有一定颠覆性的新异步方案。
“回调函数” 时期
事件监听:任务的执行顺序与代码的编写顺序无关,只与点击事件有没有被触发有关
发布订阅:任务执行的时机和某一事件的发生紧密关联了起来。
回调函数:回调地狱导致可读性和可维护性被破坏
Promise
说说你理解的 Promise(三种状态,两个过程)
Promise 对象是一个代理对象。它接受你传入的 executor(执行器)作为入参,允许你把异步任务的成功和失败分别绑定到对应的处理方法上去。一个 Promise 实例有三种状态:
• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态;
• fulfilled(resolved) 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
• rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态;
Promise实例的状态是可以改变的,但它只允许被改变一次。当我们的实例状态从 pending 切换为 rejected 后,就无法再扭转为 fulfilled,反之同理。当 Promise 的状态为 resolved 时,会触发其对应的 then 方法入参里的 onfulfilled 函数;当 Promise 的状态为 rejected 时,会触发其对应的 then 方法入参里的 onrejected 函数。
Promise 常见方法有哪些?各自是干嘛的?
Promise的方法有以下几种:
• Promise.all(iterable):这个方法返回一个新的 promise 对象,该 promise 对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。
var p1 = Promise.resolve('1号选手'); var p2 = '2号选手'; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, "3号选手"); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // ["1号选手", "2号选手", "3号选手"] });
使用场景:执行某个操作需要依赖多个接口请求回的数据,且这些接口之间不存在互相依赖的关系。这时使用Promise.all()
,等到所有接口都请求成功了,它才会进行操作。
let promise1 = new Promise((resolve) => { setTimeout(() => { resolve('promise1操作成功'); console.log('1') }, 3000); }); let promise2 = new Promise((resolve) => { setTimeout(() => { resolve('promise1操作成功'); console.log('2') }, 1000); }); Promise.all([promise1, promise2]).then((result) => { console.log(result); });
• Promise.race(iterable):当 iterable 参数里的任意一个子 promise 被成功或失败后,父 promise 马上也会用子 promise 的成功返回值或失败详情作为参数调用父 promise 绑定的相应处理函数,并返回该 promise 对象。
var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "1号选手"); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 50, "2号选手"); }); // 这里因为 2 号选手返回得更早,所以返回值以 2号选手为准 Promise.race([p1, p2]).then(function(value) { console.log(value); // "2号选手" });
let promise1 = new Promise((resolve) => { setTimeout(() => { resolve('promise1操作成功'); console.log('1') }, 3000); }); let promise2 = new Promise((resolve, reject) => { setTimeout(() => { reject('promise1操作失败'); console.log('2') }, 1000); }); Promise.race([promise1, promise2]) .then((result) => { console.log(result); }) .catch((error) => { console.log(error); })
1s后,promise2进入rejected状态,由于一个实例的状态发生了变化,所以Promise.race()就立刻执行了,其他实例中再发生变化,它也不管了。
- Promise.reject(reason): 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
- Promise.resolve(value):它返回一个 Promise 对象,但是这个对象的状态由你传入的value决定,情形分以下两种:
- 如果传入的是一个带有 then 方法的对象(我们称为 thenable 对象),返回的Promise对象的最终状态由then方法执行决定
- 否则的话,返回的 Promise 对象状态为 fulfilled,同时这里的 value 会作为 then 方法中指定的 onfulfilled 的入参
真题分析
Promise 中的处理函数是异步任务
const promise = new Promise((resolve,reject) =>{ console.log(1); resolve(); console.log(2); }) promise.then(()=>{ console.log(3); }) console.log(4);
then 方法中传入的任务是一个异步任务。resolve() 这个调用,作用是将 Promise 的状态从 pending 置为 fulfilled,这个新状态会让 Promise 知道“我的 then 方法中那个任务可以执行了”——注意是”可以执行了“,而不是说”立刻就会执行“。毕竟作为一个异步任务,它的基本修养就是要等同步代码执行完之后再执行。所以说数字 3 的输出排在最后。
Promise 对象的状态只能被改变一次
const promise = new Promise((resolve,reject) =>{ resolve('第1次resolve') console.log('resolve后的普通逻辑1'); reject('error') resolve('第2次resolve'); console.log('resolve后的普通逻辑2'); }) promise.then((res)=>{ console.log('then:',res); }).catch((err)=>{ console.log('catch',err); })
这段代码里,promise 初始状态为 pending,我们在函数体第一行就用 resolve 把它置为了 fulfilled 态。这个切换完成后,后续所有尝试进一步作状态切换的动作全部不生效,所以后续的 reject、resolve大家直接忽略掉就好;需要注意的是,我们忽略的是第一次 resolve 后的 reject、resolve,而不是忽略它身后的所有代码。因此 console.log(‘resolve后的普通逻辑’) 这句,仍然可以正常被执行。至于这里为啥它输出在 ”then: 第 1 次 resolve“ 的前面,原因和上一题是一样一样的~
Promise 值穿透
Promise.resolve(1) .then(Promise.resolve(2)) .then(3) .then() .then(console.log)
then 方法里允许我们传入两个参数:onFulfilled
(成功态的处理函数)和 onRejected
(失败态的处理函数)。
你可以两者都传,也可以只传前者或者后者。但是无论如何,then 方法的入参只能是函数。万一你想塞给它一些乱七八糟的东西,它就会“翻脸不认人”。
具体到我们这个题里,第一个 then 方法中传入的是一个 Promise 对象,then 说:”我不认识“;第二个 then 中传入的是一个数字, then 继续说”我不认识“;第四个干脆啥也没穿,then 说”入参undefined了,拜拜“;直到第五个入参,一个函数被传了进来,then 哭了:”终于等到一个我能处理的!“,于是只有最后一个入参生效了。
在这个过程中,我们最初 resolve 出来那个值,穿越了一个又一个无效的 then 调用,就好像是这些 then 调用都是透明的、不存在的一样,因此这种情形我们也形象地称它是 Promise 的“值穿透”。
实例
现在有三个请求,请求A、请求B、请求C。请求C要将请求B的请求回来的数据做为参数,请求B要将请求A的请求回来的数据做为参数。
回调写法
$.ajax({ success:function(res1){ //------请求B 开始---- $.ajax({ success:function(res2){ //----请求C 开始--- $.ajax({ success:function(res3){ } }); //---请求C 结束--- } }); //------请求B 结束----- } }); //------请求A 结束---------
promise写法
let promise = new Promise((resolve, reject) => { if (true) { //调用操作成功方法 resolve('操作成功'); } else { //调用操作异常方法 reject('操作异常'); } }); //then处理操作成功,catch处理操作异常 promise.then(requestA) .then(requestB) .then(requestC) .catch(requestError); function requestA() { console.log('请求A成功'); return '下一个是请求B'; } function requestB(res) { console.log('上一步的结果:' + res); console.log('请求B成功'); return '下一个是请求C'; } function requestC(res) { console.log('上一步的结果:' + res); console.log('请求C成功'); } function requestError() { console.log('请求失败'); }