Promise JavaScript 中存在很多异步操作,Promise 将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。可以通过链式调用多个 Promise 达到我们的目的。
Promise 在各种开源库中已经实现,现在标准化后被浏览器默认支持。
promise 是一个拥有 then 方法的对象或函数
问题探讨 下面通过多个示例来感受一下不使用 promise 时,处理相应问题的不易,及生成了不便阅读的代码。
定时嵌套 下面是一个定时器执行结束后,执行另一个定时器,这种嵌套造成代码不易阅读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <style> div { position: absolute; width: 100px; height: 100px; background-color: pink; } </style> <body> <div></div> </body> <script> function createInterval(callback, delay = 50) { let timer = setInterval(() => { callback(timer); }, delay); } const div = document.querySelector("div"); console.log(parseFloat(window.getComputedStyle(div).left)); function run() { createInterval((timer) => { div.style.left = parseFloat(window.getComputedStyle(div).left) + 2 + "px"; // console.log("left"); if (parseFloat(div.style.left) >= 50) { clearInterval(timer); createInterval((timer) => { div.style.width = parseFloat(window.getComputedStyle(div).width) - 2 + "px"; // console.log("width"); parseFloat(div.style.width) <= 0 && clearInterval(timer); }); } }); } div.addEventListener("click", run); </script>
图片加载 下面是图片后设置图片边框,也需要使用回调函数处理,代码嵌套较复杂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function createImage(file, resolve, reject) { const img = new Image(); img.src = file; img.onload = () => { resolve(img); console.log("image Loading fulfilled"); }; img.onerror = () => { console.log("image loading fail"); }; document.body.append(img); } createImage("./images/img1.jpg", (img) => { img.style.border = "5px solid pink"; });
加载文件 下面是异步加载外部JS文件,需要使用回调函数执行,并设置的错误处理的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function loadJS(file, resolve, reject) { let script = document.createElement("script"); script.src = file; script.onload = resolve; script.onerror = reject; document.body.append(script); } loadJS( "./js/ashun.js", (script) => { console.log(`${script.path[0].src} load Resolve`); title(); }, (err) => console.log(`${err.srcElement.src} load Reject`) );
实例中用到的 ashun.js 与 SHUN.js 内容如下
1 2 3 4 5 6 7 8 9 10 # ashun.js function title() { console.log("title method"); } # SHUN.js function run() { title(); console.log("run method"); }
如果要加载多个脚本时需要嵌套使用,下面SHUN.js 依赖 ashun.js,需要先加载ashun.js 后加载SHUN.js
不断的回调函数操作将产生回调地狱,使代码很难维护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function loadJS(file,resolve,reject){……} loadJS( "/js/ashun.js", (script) => { loadJS( "/js/SHUN.js", (script) => { run(); console.log(`${script.path[0].src} load Resolve`); }, (err) => { console.log(`${err.srcElement.src}加载失败`); } ); console.log(`${script.path[0].src} load Resolve`); }, (err) => { console.log(`${err.srcElement.src}加载失败`); } );
异步请求 使用传统的异步请求也会产生回调嵌套的问题。
比如若要获取商品的详情,就要分为两步
先请求商品数据,得到商品的id
根据商品id请求获取对应商品的详情数据。
以下接口无实际用途,仅作示例,可自己编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function ajax(url, resolve, reject) { let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.send(); xhr.onload = function() { if (this.status == 200) { resolve(JSON.parse(this.response)); } else { reject(this); } }; } ajax("http://localhost:8888/goodslist/data, goods => { ajax( `http://localhost:8888/category?id=${goods["id"]}`, response => { console.log(response[0]); } ); });
肯德基 下面是模拟肯德基吃饭的事情,使用 promise 操作异步的方式每个阶段会很清楚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 let kfc = new Promise((resolve, reject) => { console.log("肯德基正在为您做餐……"); setTimeout(() => { resolve("餐品已经做好,为您上桌"); }, 2000); }); let as = kfc.then((result) => { console.log(result); console.log("阿顺收到消息"); return { then(resolve, reject) { setTimeout(() => { resolve("我吃了2秒,不辣,张三你可以吃了"); }, 2000); }, }; }); let zhangsan = as.then((msg) => { return new Promise((resolve, reject) => { console.log("张三收到阿顺消息:" + msg); setTimeout(() => { let msg = "我吃了1秒,真好吃, 李四也尝尝吧"; console.log(msg); resolve(msg); }, 1000); }); }); let lisi = zhangsan.then((result) => { console.log("李四收到消息:" + result); setTimeout(() => { console.log("李四:我吃了1秒,不错不错"); }, 1000); });
而使用以往的回调方式,就会让人苦不堪言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function notice(msg, then) { then(msg); } function meal() { notice("肯德基厨房开始做饭", (msg) => { console.log(msg); notice("我是肯德基,你的餐已经做好", (msg) => { console.log(`阿顺收到肯德基消息: ${msg}`); setTimeout(() => { notice("张三,我吃了两秒了,不辣,你可以吃了", (msg) => { console.log(`张三收到阿顺消息: ${msg}`); setTimeout(() => { notice("很好吃,李四也尝尝吧", (msg) => { console.log(msg); notice("李四收到消息", (msg) => { console.log(msg); setTimeout(() => { console.log("李四:不错不错"); }, 1000); }); }); }, 2000); }); }, 2000); }); }); } meal();
异步状态 Promise 意为承诺,就像我们去饭店用餐,点完餐后服务员会给我们一个票号,如果饭菜做好,我们凭借票号去领餐,这就代表成功状态,若由于其它原因饭菜不能够顺利做好,这就代表拒绝状态。在得到这些结果前,我们一直处于等待的状态。
一个 promise 必须有一个 then 方法用于处理状态改变
状态说明 Promise包含pending、fulfilled、rejected三种状态
pending 指等待状态,初始化 promise 时的状态
resolve 指已经解决,将 promise 状态设置为fulfilled
reject 指拒绝处理,将 promise 状态设置为rejected
promise 是生产者,通过 resolve 与 reject 函数告知结果
promise 非常适合需要一定执行时间的异步任务
同一Promise状态一旦确定(fufilled\rejected),状态将不可再次更改
promise 是队列状态,就像体育中的接力赛,或多米诺骨牌游戏,状态一直向后传递,当然 也可更改其中的任何一个promise的状态,来影响后续的promise。
promise 没有使用 resolve 或 reject 更改状态时,默认状态为 pending
1 2 3 4 console.log( new Promise((resolve, reject) => { }); ); //Promise {<pending>}
当更改状态后
1 2 3 4 5 6 7 8 9 10 11 console.log( new Promise((resolve, reject) => { resolve("fulfilled"); }) ); //Promise {<resolved>: "fulfilled"} console.log( new Promise((resolve, reject) => { reject("rejected"); }) ); //Promise {<rejected>: "rejected"} //Error
若一个Promise为rejected状态,却没有处理,系统会发出警示。
1 2 3 4 5 console.log( new Promise((resolve, reject) => { reject("rejected"); }) ); //Promise {<rejected>: "rejected"} //Error:Uncaught (in promise) rejected
使用then\catch处理后,不会报错,then\catch这些方法默认也会返回一个新的promise
1 2 3 4 5 console.log( new Promise((resolve, reject) => { reject("rejected"); }).catch((err) => {}) ); //Promise {<pending>}
按理来说上述Promise已经确认为rejected状态,也经过了then\catch的onRejected回调函数处理,应该返回fufilled状态(then\catch默认返回的promise状态为fufilled),但是为什么返回pending状态呢?
console.log是同步代码会立即执行,当日志信息被打印时,promise的状态还未被确认,所以返回pending
将打印放在宏任务队列中,可看到正确结果
关于任务队列,下一章节会详细讲解
1 2 3 4 5 6 7 8 9 10 let promise = new Promise((resolve, reject) => { reject("rejected"); }); let p1 = promise.catch((err) => err); console.log(promise)//Promise {<rejected>:"rejected"} console.log(p1); //Promise {<pending>} setTimeout(() => { console.log(p1); //Promise {<fulfilled>:"rejected"} });
promise 自创建时,立即执行同步任务,then 会放在异步微任务中执行,需要等同步任务执行后才执行。
1 2 3 4 5 6 7 8 9 let promise = new Promise((resolve, reject) => { resolve("fulfilled"); console.log("阿顺"); }); promise.then(msg => { console.log(msg); }); console.log("阿顺特烦恼"); // 阿顺 阿顺特烦恼 fulfilled
1 2 3 4 5 6 const promise = new Promise((resolve) => { resolve("fulfilled"); }); promise.then(alert); alert("阿顺特烦恼"); promise.then((_) => alert("ashuntefannao"));
下例在三秒后将 Promise 状态设置为 fulfilled ,然后执行 then 方法
1 2 3 4 5 6 7 8 9 10 11 12 new Promise((resolve, reject) => { setTimeout(() => { resolve("fulfilled"); }, 3000); }).then( msg => { console.log(msg); }, error => { console.log(error); } );
状态被改变后就不能再修改了,下面先通过resolve 改变为成功状态,表示promise 状态已经完成,就不能使用 reject 更改状态了
1 2 3 4 5 6 7 8 9 10 11 new Promise((resolve, reject) => { resolve("操作成功"); reject(new Error("请求失败")); }).then( msg => { console.log(msg); }, error => { console.log(error); } );
动态改变 可以在一个promise中,通过处理另一个promise1,来改变promise的状态。
当在一个promise中,告知状态时传入的是另一个promise1,则后续使用then或其它方法对其进行处理时,处理的已经不再是promise的状态,而是promise1。
在p2中确认resolve状态,但在后续的then中却执行了rejected回调函数。
在p2中确认状态时,返回的是p1,那么后续处理的promise将是p1,p2的状态已经无意义
1 2 3 4 5 6 7 8 9 10 11 12 13 { let p1 = new Promise((resolve, reject) => { reject("p1 reject"); }); let p2 = new Promise((resolve, reject) => { console.log(`p2发送resolve状态`); resolve(p1); }).then( (result) => console.log(`p2 处理结果:fulfilled--${result}`), (err) => console.log(`p2 处理结果:rejected--${err}`) ); }
then 一个promise 需要提供一个then方法访问promise 结果,then 用于当 promise 状态发生改变时的处理,即promise处理异步操作过程,then 用于处理结果。
promise 就像 饭馆 中的厨房,then 就是我们用户,如果餐做好了即 fulfilled ,做不了拒绝即rejected 状态。那么 then 就要对不同状态处理。
then 方法 必须 返回 promise,用户返回或系统自动返回
第一个函数在resolved 状态时执行,即使用resolve确认完成状态时,执行then第一个callback处理成功状态
第二个函数在rejected状态时执行,即使用reject确认拒绝状态时,执行第二个callback处理失败状态,该函数是可选的
两个callback都接收 promise 确认状态时传入的值做为参数
也可以使用catch 来处理失败的状态
如果在 then 中手动返回 promise ,下一个then 会在上一个then返回的promise 状态改变后执行
语法说明 then的语法如下,onFulfilled 函数处理 fulfilled 状态, onRejected函数处理 rejected 状态
onFulfilled 或 onRejected 不是函数将被忽略
两个函数只会被调用一次
onFulfilled 在 promise 执行成功时调用
onRejected 在 promise 执行拒绝时调用
1 promise.then(onFulfilled, onRejected)
基础知识 then 会在 promise 确认状态后执行,then 第一个callback在 resolve成功状态执行
1 2 3 4 5 6 7 8 9 10 const promise = new Promise((resolve, reject) => { resolve("success"); }).then( value => { console.log(`解决:${value}`); }, reason => { console.log(`拒绝:${reason}`); } );
then 中第二个参数在失败状态执行
1 2 3 4 5 6 7 8 9 10 11 const promise = new Promise((resolve, reject) => { reject("is error"); }); promise.then( msg => { console.log(`成功:${msg}`); }, error => { console.log(`失败:${error}`); } );
如果只关心成功则不需要传递 then 的第二个参数
1 2 3 4 5 6 const promise = new Promise((resolve, reject) => { resolve("success"); }); promise.then(msg => { console.log(`成功:${msg}`); });
如果只关心失败时状态,then 的第一个参数传递 null
1 2 3 4 5 6 const promise = new Promise((resolve, reject) => { reject("is error"); }); promise.then(null, error => { console.log(`失败:${error}`); });
promise 确认状态后转入的参数,会传入到then对应callback的参数中,如果then没有可处理函数,会一直向后传递
1 2 3 4 5 6 7 8 let p1 = new Promise((resolve, reject) => { reject("rejected"); }) .then() .then( null, f => console.log(f) );
1 2 3 4 5 6 7 let promise = new Promise((resolve, reject) => { resolve("resolve"); }); let p2 = promise.then(); p2.then().then(resolve => { console.log(resolve); });
1 2 3 4 5 6 7 let promise = new Promise((resolve, reject) => { reject("reject"); }); let p2 = promise.then(() => {}); p2.then(null, null).then(null, reject => { console.log(reject); });
链式调用
then\catch等promise的方法,是对上一个promise状态的处理,而使用then/catch后 它们本身又会默认返回一个新的promise,且状态为fulfilled,所以then才可不断地链式调用。
当然,也可以在then\catch中手动返回自定义的promise,可通过这个promise,改变当前then的状态
每次的 then 都是一个全新的 promise, then 默认返回的 promise 状态是 fulfilled
1 2 3 4 5 6 7 8 { let promise = new Promise((resolve, reject) => { reject("promise state rejected"); }); promise .then(null, (err) => console.log(err)) .then((_) => console.log("then默认状态为fulfilled")); }
每次的 then 都是一个全新的 promise,不要认为上一个 promise状态会影响以后then返回的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let p1 = new Promise(resolve => { rejected("p1 rejected"); }); let p2 = p1.then(null,rej=>console.log(reg)); p2.then(() => { console.log("Ashuntefannao"); }); console.log(p1); // Promise {<resolved>} console.log(p2); // Promise {<pending>} setTimeout(() => { console.log(p1); // Promise {<resolved>} console.log(p2); // Promise {<resolved>} });
then 是对上个promise 的处理,每个 then 又会返回一个promise,默认传递 fulfilled状态,所以才可以不断地链式调用then处理promise。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 new Promise((resolve, reject) => { reject(); }) .then( resolve => console.log("fulfilled"), reject => console.log("rejected") ) .then( resolve => console.log("fulfilled"), reject => console.log("rejected") ) .then( resolve => console.log("fulfilled"), reject => console.log("rejected") ); # 执行结果如下 rejected fulfilled fulfilled
如果内部手动返回 promise 时,后续处理的便是该 promise,即该promise可以改变当前then的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 { let promise = new Promise((resolve, reject) => { reject("promise state rejected"); }); promise .then( (result) => console.log("fulfilled"), (reject) => console.log(reject) ) .then( (res) => console.log("fufilled"), (rej) => console.log("rejected") ) .then((res) => { console.log("fufilled"); return Promise.reject("手动返回的promise,状态为rejected"); }) .then( (res) => console.log("fufilled"), (rej) => console.log(rej) ); } # 执行结果如下 "promise state rejected" "fulfilled" "fulfilled" "手动返回的promise,状态为rejected"
若手动返回的不是Promise,则下一个then处理的还是 上一个then默认返回的Promise,状态为fulfilled。
但是下一个then对应的处理函数,会接受上一个then返回的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { let promise = new Promise((resolve, reject) => { reject("promise state rejected"); }); let p1 = promise.then(null, (rej) => { console.log(rej) return "p1 rejected"; }); let p2 = p1.then((res) => { console.log(`p2 fulfilled \n${res}`); }); } # 执行结果如下 "promise state rejected" "p2 fulfilled p1 rejected"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 new Promise((resolve, reject) => { resolve(); }) .then(v => { return new Promise((resolve, reject) => { resolve("第二个promise"); }); }) .then(value => { console.log(value); return value; }) .then(value => { console.log(value); });
then是处理上一个Promise的结果,只有上一个promise确认了状态,then才会执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("promise fulfilled"); }, 1000); }) .then((res) => { console.log(res); return new Promise((resolve) => { setTimeout(() => { resolve("resolved"); }, 1000); }); }) .then((res) => console.log(res)); }
循环调用
如果 then 的返回值与所处理的 promise 相同将禁止执行,(避免陷入死循环)
1 2 3 4 5 6 let promise = new Promise(resolve => { resolve(); }); let p2 = promise.then(() => { return p2; }); // TypeError: Chaining cycle detected for promise
其它类型 then able :具有then能力的类型
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
也就是说,如果x具有then方法,那么将会被系统认为是类promise的结构
若在Promise中返回x,则使用then处理Promise时,系统会尝试让原Promise的then方法接收x中then方法的状态
包含then方法的Object可以当作promise使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { let promise = new Promise((resolve, reject) => { resolve({ then(resolve, reject) { resolve("promise fulfilled"); }, }); }); let p1 = promise.then((result) => { console.log(result); return { then(resolve, reject) { setTimeout(() => { reject(" p1 rejected"); }, 1000); }, }; }); p1.then( (mgs) => { console.log(mgs); }, (rej) => console.log(rej) ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Goods { constructor(id) { this.id = id; } then(resolve, reject) { resolve(ajax(`http://localhost:8083/goods?id=${this.id}`)); } } new Promise((resolve, reject) => { resolve(ajax(`http://localhost:8083/goodsList?type=new`)); }) .then(goods => { return new Goods(goods.id); }) .then(category => { console.log(category); });
当然也可以是类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 new Promise((resolve, reject) => { resolve( class { static then(resolve, reject) { setTimeout(() => { resolve("解决状态"); }, 2000); } } ); }).then( v => { console.log(`fulfilled: ${v}`); }, v => { console.log(`rejected: ${v}`); } );
如果对象中的 then 不是函数,则将对象做为值传递
1 2 3 4 5 6 7 8 9 10 11 new Promise((resolve, reject) => { resolve(); }) .then(() => { return { then: "阿顺特烦恼" }; }) .then(v => { console.log(v); //{then: "阿顺特烦恼"} });
catch 基础知识 下面使用未定义的变量同样会触发失败状态
1 2 3 4 5 6 let promise = new Promise((resolve, reject) => { as; }).then( value => console.log(value), reason => console.log(reason) );
如果 onFulfilled 或 onRejected 抛出异常,则 p2 拒绝执行并返回拒因
1 2 3 4 5 6 7 let promise = new Promise((resolve, reject) => { throw new Error("fail"); }); let p2 = promise.then(); p2.then().then(null, resolve => { console.log(resolve + ",阿顺特烦恼"); });
catch(err=>{})是用来接收Promise拒绝状态的。经catch处理后,默认返回的promise状态也为fulfilled
catch相当于then(null,onReject)
若某个promise已被then(null,onReject)处理,则catch将不会接收该拒绝状态
1 2 3 4 5 6 7 8 9 { let promise = new Promise((resolve, reject) => { reject("rejected"); }); let p1 = promise.catch((err) => console.log(err)); setTimeout(() => { console.log(p1); }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { let promise = new Promise((resolve, reject) => { reject("promise rejected"); }); promise .then(null, (resolve) => { console.log(resolve + ",阿顺特烦恼"); }) .catch((err) => console.log(err)); } // promise rejected,阿顺特烦恼 { let promise = new Promise((resolve, reject) => { reject("promise rejected"); }); promise .catch((err) => console.log(err)) .then(null, (resolve) => { console.log(resolve + ",阿顺特烦恼"); }); } // promise rejected
建议使用 catch 处理错误
将 catch 放在最后面用于统一处理前面发生的错误
1 2 3 4 5 const promise = new Promise((resolve, reject) => { reject(new Error("Notice: Promise Exception")); }).catch(msg => { console.error(msg); });
catch 可以捕获之前所有 promise 的错误,所以建议将 catch 放在最后。下例中 catch 也可以捕获到了第一个 then 返回 的 promise 的错误。
1 2 3 4 5 6 7 8 9 10 11 12 new Promise((resolve, reject) => { resolve(); }) .then(() => { return new Promise((resolve, reject) => { reject("err from .then "); }); }) .then(() => {}) .catch(msg => { console.log(msg); });
错误是冒泡的操作的,下面没有任何一个then 定义第二个函数参数onRejected,将一直冒泡到 catch 处理错误
1 2 3 4 5 6 7 8 new Promise((resolve, reject) => { reject(new Error("请求失败")); }) .then(msg => {}) .then(msg => {}) .catch(error => { console.log(error); });
catch 也可以捕获对 then 抛出的错误处理
1 2 3 4 5 6 7 8 9 new Promise((resolve, reject) => { resolve(); }) .then(msg => { throw new Error("这是then 抛出的错误"); }) .catch(() => { console.log("33"); });
catch 也可以捕获其他错误,下面在 then 中使用了未定义的变量,将会把错误抛出到 catch
1 2 3 4 5 6 7 8 9 new Promise((resolve, reject) => { resolve("success"); }) .then(msg => { console.log(a); }) .catch(reason => { console.log(reason); });
处理机制 1 2 3 4 5 const promise = new Promise((resolve, reject) => { throw new Error("fail"); }).catch(msg => { console.log(msg.toString()+"阿顺特烦恼"); });
可以将上面的理解为如下代码,可以理解为内部自动执行 try...catch
1 2 3 4 5 6 7 8 9 const promise = new Promise((resolve, reject) => { try { throw new Error("fail"); } catch (error) { reject(error); } }).catch(msg => { console.log(msg.toString()); });
但像下面的在异步宏任务中 throw new Error 将不会触发 catch,而使用系统错误处理
1 2 3 4 5 6 7 const promise = new Promise((resolve, reject) => { setTimeout(() => { throw new Error("fail"); }, 2000); }).catch(msg => { console.log(msg + "阿顺特烦恼"); });
在 catch 中发生的错误也会抛给最近的错误处理
1 2 3 4 5 6 7 8 9 const promise = new Promise((resolve, reject) => { reject(); }) .catch(msg => { ashun(); }) .then(null, error => { console.log(error); });
定制错误 可以根据不同的错误类型进行定制操作,下面将url格式错误与参数错误分别进行了处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class ParamError extends Error { constructor(msg) { super(msg); this.name = "ParamError"; } } class HttpError extends Error { constructor(msg) { super(msg); this.name = "HttpError"; } } function ajax(url) { return new Promise((resolve, reject) => { if (!/^http/.test(url)) { throw new HttpError("请求地址格式错误"); } let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.send(); xhr.onload = function() { if (this.status == 200) { resolve(JSON.parse(this.response)); } else if (this.status == 404) { // throw new ParamError("用户不存在"); reject(new ParamError("用户不存在")); } else { reject("加载失败"); } }; xhr.onerror = function() { reject(this); }; }); } ajax(`http://localhost:8083?name=阿顺特烦恼`) .then(value => { console.log(value); }) .catch(error => { if (error instanceof ParamError) { console.log(`参数错误:${error.message}`); } if (error instanceof HttpError) { alert(`url格式错误:${error.message}`); } console.log(error); });
finally 无论状态是resolve 或 reject 都会执行此动作,finally 与状态无关。
根据finally的特性,通常在其中做一些公共的操作,不论状态,都会执行的操作。
1 2 3 4 5 6 7 8 9 10 11 12 const promise = new Promise((resolve, reject) => { reject("阿顺特烦恼"); }) .then(msg => { console.log("resolve"); }) .catch(msg => { console.log("reject"); }) .finally(() => { console.log("resolve/reject状态都会执行"); });
下面使用 finally 处理加载状态,当图片加载完成时移除加载图标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <style> div { position: absolute; width: 100px; height: 100px; background-color: pink; } .result { display: none; } </style> <body> <div class="loading">loading...</div> <div class="result"></div> </body> <script> function createImage(file) { return new Promise((resolve, reject) => { let img = new Image(); img.src = file; img.onload = () => resolve(img); img.onerror = () => reject("加载失败"); }); } setTimeout(() => { createImage("./images/img1.jpg") .then((img) => { document.body.append(img); document.querySelector(".result").innerHTML = "加载成功"; }) .catch((err) => { console.log(err); document.querySelector(".result").innerHTML = err; }) .finally(() => { document.querySelector("div").style.display = "none"; document.querySelector(".result").style.display = "block"; }); }, 1000); </script>
实例操作 异步请求 下面是将 ajax 请求使用 promise 处理,代码结构清晰了很多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function ajax(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.send(); xhr.onload = function() { if (this.status == 200) { resolve(JSON.parse(this.response)); } else { reject(this); } }; }); } ajax("http://localhost:8083/goodsList?name=new") .then(goods =>ajax(`http://localhost:8083/category?id=${goods["id"]}`)) .then(data => { console.log(data); });
脚本加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 { function loadJS(file) { return new Promise((resolve, reject) => { let script = document.createElement("script"); script.src = file; document.body.append(script); script.onload = () => { resolve(script); }; script.onerror = () => { reject(script); }; }); } loadJS("./js/ashun.js") .then((script) => { title(); console.log("ashun.js finishLaod"); return script; }) .then((ashunjs) => { return loadJS("./js/SHUN.js").then((script) => { console.log("SHUN.js finishLaod"); run(); }); }) .catch((script) => console.log(`${script.src}:加载失败`)); }
定时器 下面是封装的timeout 函数,使用定时器操作更加方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function timeout(times) { return new Promise(resolve => { setTimeout(resolve, times); }); } timeout(3000) .then(() => { console.log("3秒后执行"); return timeout(1000); }) .then(() => { console.log("执行上一步的promise后1秒执行"); });
使用Promise封装 setInterval 定时器并实现动画效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <style> div { width: 100px; height: 100px; background: pink; position: absolute; } </style> <body> <div></div> </body> <script> function interval(callback, delay = 5) { return new Promise((resolve, reject) => { let id = setInterval(() => { callback(id, resolve); }, delay); }); } let div = document.querySelector("div"); function run() { const step = 2; interval((timer, resolve) => { let left = parseFloat(window.getComputedStyle(div).left); if (left >= 200) { clearInterval(timer); resolve("left end"); } div.style.left = left + step + "px"; }) .then((result) => { console.log(result); return interval((timer, resolve) => { let width = parseFloat(window.getComputedStyle(div).width); if (width <= 0) { clearInterval(timer); resolve("width end"); } div.style.width = width - step + "px"; }); }) .then((result) => console.log(result)); } div.addEventListener("click", run); </script>
链式操作
每个 then 都是一个promise,then会默认返回一个promise,且状态为fulfilled。
如果在 then 中手动返回一个新promsie,只当这个新的promise 确认状态后,才会继承执行下一个 then
语法介绍 下面是对同一个 promise 分别使用不同的 then 进行操作 ,每个then 都得到了同一个promise 结果,这不是链式操作,实际使用意义不大。
1 2 3 4 5 6 7 8 9 10 11 const promise = new Promise((resolve, reject) => { resolve("阿顺特烦恼"); }); promise.then(title => { title += "-Ashun"; console.log(title); //阿顺特烦恼-Ashun }); promise.then(title => { title += "-Ashuntefannao"; console.log(title); //阿顺特烦恼-Ashuntefannao });
promise 中的 then 方法可以链式调用,then 方法的返回值会传递到下一个then 方法对应的处理函数中。
then 会返回一个promise ,所以如果有多个then 时会连续执行
then 返回的值会做为当前promise 的结果
下面是链式操作的 then,即始没有 return 也是会执行,因为每个then 默认会返回promise
1 2 3 4 5 6 7 8 9 10 11 12 new Promise((resolve, reject) => { resolve("阿顺特烦恼"); }) .then((title) => { title += "-Ashun"; console.log(title); //阿顺特烦恼-Ashun return title; }) .then((title) => { title += "-Ashuntefannao"; console.log(title); //阿顺特烦恼-Ashun-Ashuntefannao });
then 方法可以返回一个新的promise 对象,等返回的promise 确认状态后,才会执行后面的 then。后面的then 方法就是对新返回的promise 状态的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 new Promise((resolve, reject) => { resolve("第一个promise"); }) .then(msg => { console.log(msg); return new Promise((resolve, reject) => { setTimeout(() => { resolve("第二个promise"); }, 3000); }); }) .then(msg => { console.log(msg); });
静态方法 Promise类还提供一些静态方法,能够更好的处理异步操作。
这些静态方法参数中若传入非Promise数据类型,则会将其转化为Promise,除了reject静态方法会默认将其转化为Promise.resolve,其余静态方法都会将其转化为Promise.resolve,数据本身作为返回结果。
resolve 使用 promise.resolve(msg) 方法可以快速的返回一个状态为resolve的promise对象。
1 console.log(Promise.resolve("fulfilled")); //Promise {<fulfilled>: "fulfilled"}
1 2 3 4 5 6 7 8 9 10 11 12 { let promise = new Promise((resolve, reject) => { reject("promise state: rejected"); }); promise .then(null, (rej) => { console.log(rej); return Promise.resolve("阿顺特烦恼"); }) .then((result) => console.log(result)); }
若传入值为promise,后续then处理的是传入的promsie
1 Promise.resolve(Promise.reject("阿顺特烦恼")).catch((rej) =>console.log(rej));
下面将请求结果缓存,如果再次请求时直接返回带值的 promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function query(name) { const cache = query.cache || (query.cache = new Map()); if (cache.has(name)) { console.log("走缓存了"); return Promise.resolve(cache.get(name)); } return ajax(`http://localhost:8083/users?name=${name}`).then( response => { cache.set(name, response); console.log("没走缓存"); return response; } ); } query("阿顺").then(response => { console.log(response); }); setTimeout(() => { query("阿顺").then(response => { console.log(response); }); }, 1000);
如果是 then able (具有then能力,类似promise)对象,会将对象包装成promise处理,这与其他promise处理方式一样的
1 2 3 4 5 6 7 8 const as = { then(resolve, reject) { resolve("阿顺特烦恼"); } }; Promise.resolve(as).then(value => { console.log(value); });
若传入非Promise数据,默认将其转化为Promise.resolve,数据本身作为Promise.resolve的返回值
1 2 3 4 5 function getName() { return "ashun"; } Promise.resolve(getName).then((result) => console.log(result())); //ashun
reject 和 Promise.resolve 类似,reject 生成一个拒绝状态的promise
1 Promise.reject("fail").catch(error => console.log(error));
下面使用 Project.reject 设置状态
1 2 3 4 5 6 7 8 9 new Promise(resolve => { resolve("阿顺特烦恼"); }) .then(v => { if (v != "Ashuntefannao") return Promise.reject(new Error("not Ashuntefannao")); }) .catch(error => { console.log(error); });
若传入非Promise数据,默认将其转化为Promise.reject数据本身作为Promise.reject的返回值
1 2 3 let obj = {name:"Ashun"}; Promise.reject(obj).catch((reason) => console.log(reason.name)); //Ashun
all 使用Promise.all(promiseArr) 方法可以同时执行多个异步操作,比如页面加载时同进获取课程列表与推荐课程。
任何一个 Promise 执行失败就会调用 catch方法
任何一个Promise没有确认状态,则all默认返回的promise状态也为pending,也就不会执行后续的then
适用于一次发送多个异步操作
参数必须是可迭代类型,如Array/Set
成功后返回包含 promise 结果的有序数组
若传入非Promise数据,则将其自动转化为Promise.resolve,数据本身作为返回值
下例中当 p1、p2 两个 Promise 状态都为 fulfilled 时,p3状态才为fulfilled。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { let p1 = new Promise((resolve) => { setTimeout(() => { resolve("p1 state: fulfilled"); }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p2 state: fulfilled"); }, 2000); }); let p3 = Promise.all([p1, p2]).then( (result) => console.log(result), (err) => console.log(err) ); }
若其中有一个promise状态为reject,则将调用Promise.all()后续then中的onReject,或catch方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { let p1 = new Promise((resolve) => { setTimeout(() => { resolve("p1 state: fulfilled"); }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("p2 state: reject"); }, 2000); }) let p3 = Promise.all([p1, p2]).then( (result) => console.log(result), (err) => console.log(err) ); }
1 2 3 4 ………… let p3 = Promise.all([p1, p2]).catch( (err) => console.log(err) );
若某个状态为rejected的Promise已经被自身的then处理 , 那么该Promise最后返回的状态是fulfilled,(then默认返回状态为fulfilled的Promise )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { let p1 = new Promise((resolve) => { setTimeout(() => { resolve("p1 state: fulfilled"); }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject("p2 state: reject"); }, 2000); }).then(null, (err) => err); let p3 = Promise.all([p1, p2]) .then((result) => console.log(result)) .catch((err) => console.log("有promise状态为rejected")); // ["p1 state: fulfilled", "p2 state: reject"] }
根据用户名获取用户,有任何一个用户获取不到时 promise.all 状态失败,执行 catch 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function ajax(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.send(); xhr.onload = function() { if (this.status == 200) { resolve(JSON.parse(this.response)); } else { reject(this); } }; }); } const api = "http://localhost:8083"; const promises = ["阿顺", "张三"].map(name => { return ajax(`${api}/users?name=${name}`); }); Promise.all(promises) .then(response => { console.log(response); }) .catch(error => { console.log(error); });
可以将其他非promise 数据添加到 all 中,它将被处理成 Promise.resolve,数据本身会被当做Promise.resolve的返回值
1 2 3 4 5 6 7 8 9 { let getName = () => "Ashun"; let Age = 18; let promise = new Promise((resolve) => resolve("阿顺特烦恼")); Promise.all([getName, Age, promise]).then((result) => console.log(result) ); // [ƒ, 18, "阿顺特烦恼"] }
allSettled Promise.allSettled(PromsieArr),会将所有的Promsie视为已解决状态 ,会忽略拒绝状态的Promise,
拒绝状态的Promise信息,也会返回到最终的结果中。
返回包含Promise状态和结果的有序数组
[{status:resolve/reject,value/reason},{…}]
下面的p2 返回状态为 rejected ,但promise.allSettled 不关心,promise.allSettled始终将自身状态设置为 fulfilled 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { const p1 = new Promise((resolve, reject) => { resolve("p1 resolved"); }); const p2 = new Promise((resolve, reject) => { reject("p2 rejected"); }); Promise.allSettled([p1, p2]).then((msg) => { console.log(msg); }); } # 执行结果 [ {status: "fulfilled", value: "p1 resolve"}, {status: "rejected", reason: "p2 rejected"} ]
下面是获取用户信息,但不关注某个用户是否获取不成功
1 2 3 4 5 6 7 8 const api = "http://localhost:8083"; const promises = [ ajax(`${api}/users?name=阿顺`), ajax(`${api}/users?name=张三`) ]; Promise.allSettled(promises).then(response => { console.log(response); });
其中若传入非promise数据类型,内部会转化为Promise.resolve,数据本身作为返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let arr = [ function () { return "ashun"; }, { name: "ASHUN" }, 18, ]; Promise.allSettled(arr).then((result) => console.log(result)); # 结果 [ {status: "fulfilled", value: ƒ}, {status: "fulfilled", value: {…}}, {status: "fulfilled", value: 18}, ]
race 使用Promise.race(promiseArr) 能够处理容错异步,且返回最快确定状态的Promise结果,和race单词一样哪个Promise快用哪个,哪个先返回用哪个。
以最快返回的promise为准
如果最快返加的状态为rejected 那整个Promise.rece状态为rejected执行cache
如果参数不是promise,内部将自动转为Promise.resolve
下例体现Promise.rece的特性,哪个Promise最先确定状态,就返回谁的处理结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("第一个Promise"); }, 2000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("第二个异步"); }, 1000); }); Promise.race([p1, p2]) .then(results => { console.log(results); }) .catch(msg => { console.log(msg); }); // "第二个异步"
可用其设置请求最长相应时间,下例:若请求在2秒内没有返回结果,则promise.race 状态失败,将会执行catch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const api = "http://localhost:8083"; const promises = [ ajax(`${api}/users.php?name=阿顺`), new Promise((a, b) => setTimeout(() => b(new Error("request fail")), 2000) ) ]; Promise.race(promises) .then(response => { console.log(response); }) .catch(error => { console.log(error); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { let promises = [ new Promise((resolve) => { setTimeout(() => { resolve("请求成功"); }, 1200); }), new Promise((resolve, reject) => { setTimeout(() => { reject("请求超时"); }, 1100); }), ]; Promise.race(promises) .then((result) => console.log(result)) .catch((reason) => console.log(reason)); }
如果参数不是promise,内部将自动转为Promise.resolve,且数据本身作为Promise.resolve的返回值
1 2 3 4 5 6 7 8 9 10 { let arr = [ function () { return "ashun"; }, { name: "ASHUN" }, 18, ]; Promise.race(arr).then((result) => console.log(result())); //ashun }
任务队列 实现原理
如果在then中手动返回promise 时,后面的then 就是对返回的 promise 的处理
通过链式调用then,不断处理返回的Promsie,解决任务队列中的任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let promise = Promise.resolve(); let p1 = promise.then(() => { return new Promise(resolve => { setTimeout(() => { console.log(`p1`); resolve(); }, 1000); }); }); p1.then(() => { return new Promise((a, b) => { setTimeout(() => { console.log(`p2`); }, 1000); }); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { let promise = Promise.resolve(); promise = promise.then((result) => { return new Promise((resolve, reject) => { console.log("第一个then"); resolve(2); }); }); promise = promise.then((index) => { return new Promise((resolve, reject) => { console.log(`第${index}个then`); resolve(3); }); }); promise = promise.then((index) => { return new Promise((resolve, reject) => { console.log(`第${index}个then`); resolve(4); }); }); }
下面使用 Array.map 构建的队列,有以下几点需要说明
then 内部返回的 Promise 更改外部的 promise 变量
为了让任务继续下去,执行完任务需要将 promise 状态修改为 fulfilled
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { function queue(tasks) { let promise = Promise.resolve(); tasks.map((val, index) => { promise = promise.then((result) => { result && console.log(result); return new Promise((resolve, reject) => { console.log(val); resolve( `第${index + 1}个任务执行完毕,第${index + 2}个任务开始执行` ); }); }); }); } queue(["shun-1", "Ashun-2", "Ashuntefannao-3"]); } # 执行结果 shun-1 第1个任务执行完毕,第2个任务开始执行 Ashun-2 第2个任务执行完毕,第3个任务开始执行 Ashuntefannao-3
下面再来通过 reduce 来实现队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { function queue(tasks) { tasks.reduce((promise, val, index) => { promise = promise.then((result) => { result && console.log(result); return new Promise((resolve, reject) => { console.log(val); resolve( `第${index + 1}个任务执行完毕,第${index + 2}个任务开始执行` ); }); }); return promise; }, Promise.resolve()); } queue(["shun-1", "Ashun-2", "Ashuntefannao-3"]); }
高可用封装 上例中处理是在队列中完成,不方便业务定制,下面将Promise处理在剥离到外部
后台请求处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export default function(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() xhr.open('GET', url) xhr.send() xhr.onload = function() { if (this.status === 200) { resolve(this.response) } else { reject(this) } } }) }
队列处理类
1 2 3 export default function(promises) { promises.reduce((promise, next) => promise.then(next), Promise.resolve()) }
后台脚本
1 2 3 4 5 6 7 8 <?php $users = [ 1 => "小明", 2 => "李四", 3 => "张三" ]; sleep(1); echo $users[$_GET['id']];
使用队列
1 2 3 4 5 6 7 8 9 <script type="module"> import queue from './queue.js' import axios from './axios.js' queue( [1, 2, 3].map(v => () => axios(`user.php?id=${v}`).then(user => console.log(user)) ) ) </script>
async/await async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。
async/await 本质还是promise,只是更简洁的语法糖书写
async/await 使用更清晰的结构来替换 promise.then/catch 的方式
async/await能够让异步代码,以同步的形式 按顺序执行
async声明的函数,会自动返回一个Promise。
await必须在async声明的函数中使用。
async 下面在 fun 函数前加上async,函数将默认返回一个状态为fulfilled的promise,函数体中使用return返回的数据,可以在后续的then中接收到
1 2 3 4 async function fun() { return "阿顺特烦恼"; } fun().then((result) => console.log(result));
若要按顺序处理多个异步代码块,使用传统的promise.then/catch在整个代码结构上,不如使用async/await清晰。
下例通过处理相同的问题,进行对比
使用传统Promise处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { function promise(text) { return new Promise((resolve, reject) => { resolve(text || "第一个then"); }); } promise() .then((result) => { console.log(result); return promise("第二个then"); }) .then((result) => { console.log(result); return promise("第三个then"); }) .then((result) => { console.log(result); }); }
使用async/await处理可观察到,代码是按照同步形式执行的,结构更加清晰,代码量也减少了许多。
await相当于then,能够传递Promise的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { async function promise(text) { return text || "第一个then"; } async function run() { let result1 = await promise(); console.log(result1); let result2 = await promise("第二个then"); console.log(result2); let result3 = await promise("第三个then"); console.log(result3); } run(); }
await await关键词后面紧跟Promise,使用 await 关键词,可以接收所处理的Promise的返回值,并且等待该Promise确定状态后,才可执行后面的代码。
await 后面一般是promise,如果不是直接返回
await 必须放在 async 定义的函数中使用
await 用于替代 then 使编码更优雅
下例会在 await 这行等待promise执行,直到 promise 确认状态后才执行后续代码。
1 2 3 4 5 6 7 8 9 10 async function fun() { const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Ashuntefannao"); }, 2000); }); let result = await promise; console.log(result); } fun();
我们知道:在then中不能够返回所处理的Promise本身,会产生死循环
1 let p1 = Promise.resolve().then((_) => p1);
同理在async声明的函数中,也不能使用await处理函数本身。
等待fun返回的promise确认状态,而fun在调用时,又会等待自身。
1 2 3 4 async function fun() { await fun(); } fun();
下面是请求后台获取用户课程成绩的示例
1 2 3 4 5 6 7 async function user() { let user = await ajax(`http://localhost:8083/users?name=向军`); let lessons = await ajax( `http://localhost:8083/lessons?id=${user.id}` ); console.log(lessons); }
也可以将操作放在立即执行函数中完成
1 2 3 4 5 6 7 (async () => { let user = await ajax(`http://localhost:8083/users?name=向军`); let lessons = await ajax( `http://localhost:8083/lessons?id=${user.id}` ); console.log(lessons); })();
下面是使用async 设置定时器,并间隔时间来输出内容
1 2 3 4 5 6 7 8 9 10 11 12 async function sleep(ms = 2000) { return new Promise(resolve => { setTimeout(resolve, ms); }); } async function run() { for (const value of ["阿顺特烦恼", "Ashun"]) { await sleep(); console.log(value); } } run();
加载进度 下面是模拟请求后台查看进度,进度条展示的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <style> div { width: 0px; height: 30px; border-radius: 3px; overflow: hidden; background-color: yellowgreen; color: white; text-align: center; } </style> <body> <div class="loading"></div> </body> <script> { //随机产生小于3秒的请求时间。 function randomTime() { let max = 3; let min = 1; let multiple = Math.floor(Math.random() * (1000 - 100 + 1) + 100); return Math.floor(Math.random() * (max - min + 1) + min) * multiple; } //模拟请求延时。 async function request() { return new Promise((resolve) => setTimeout(resolve, randomTime())); } let div = document.querySelector(".loading"); //请求任务队列 let requsets = [request, request, request, request, request]; let sum = 0; requsets.map(async (req) => { await req(); let percent = ++sum / requsets.length; div.style.width = percent * 200 + "px"; if (percent == 1) { div.innerHTML = "加载完毕"; } else { div.innerHTML = percent * 100 + "%"; } }); } </script>
then able 和 Promise 一样,await 也可以操作then able 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class User { constructor(name) { this.name = name; } then(resolve, reject) { let user = ajax(`http://localhost:8083/user?name=${this.name}`); resolve(user); } } async function get() { let user = await new User("阿顺"); console.log(user); } get();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { let user = { name: "阿顺", then(resolve, reject) { return Promise.resolve().then((_) => setTimeout(resolve.bind(null, this.name), 1000) ); }, }; async function run() { let result = await user; console.log(result); } run(); }
对象、类中的方法,也可以用async/await处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { let user = { name: "阿顺", then(resolve, reject) { return Promise.resolve().then((_) => setTimeout(resolve.bind(null, this.name), 1000) ); }, }; let obj = { async getName() { let result = await user; console.log(result); }, }; obj.getName(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 class User { constructor() {} async get(name) { let user = await ajax( `http://localhost:8083/users?name=${name}` ); user.name += "-Ashuntefannao"; return user; } } new User().get("阿顺").then(resolve => { console.log(resolve); });
声明形式 函数声明
1 2 3 4 5 6 async function get(name) { return await ajax(`http://localhost:8083/users?name=${name}`); } get("阿顺").then(user => { console.log(user); });
函数表达式
1 2 3 4 5 6 let get = async function(name) { return await ajax(`http://localhost:8083/users?name=${name}`); }; get("阿顺").then(user => { console.log(user); });
对象方法声明
1 2 3 4 5 6 7 8 9 let as = { async get(name) { return await ajax(`http://localhost:8083/users?name=${name}`); } }; as.get("阿顺").then(user => { console.log(user); });
立即执行函数
1 2 3 4 5 6 7 (async () => { let user = await ajax(`http://localhost:8083/users?name="阿顺"`); let lessons = await ajax( `http://localhost:8083/lessons?id=${user.id}` ); console.log(lessons); })();
类方法中的使用
1 2 3 4 5 6 7 8 class User { async get(name) { return await ajax(`http://localhost:8083/users?name=${name}`); } } let user = new User().get("阿顺").then(user => { console.log(user); });
错误处理 async 内部发生错误,会将返回的promise状态置为rejected 状态,所以可以使用catch 来处理
1 2 3 4 5 6 async function as() { console.log(shun); } as().catch(error => { throw new Error(error); });
下面是异步请求数据不存在时的错误处理
1 2 3 4 5 6 7 async function get(name) { return await ajax(`http://localhost:8083/users?name=${name}`); } get("阿顺小哥").catch(error => { alert("用户不存在"); });
若await后的promise 被拒绝,并且没有使用then\catch处理,将抛出异常,可以使用 try...catch 处理错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { let users = ["阿顺", "Ashun", "shun"]; async function getUser(name) { let test = users.includes(name); try { await new Promise((res, rej) => { console.log(test); test ? res(test) : rej(`${name} not find`); }); } catch (err) { console.log(err); } } getUser("阿顺A"); }
多个 await 时当前面的出现失败且未处理,后面的将不可以执行
1 2 3 4 5 6 7 async function promiseQueue() { await Promise.reject("fail"); await Promise.resolve("success").then(value => { console.log(value); }); } promiseQueue();
如果对前一个错误进行了处理,后面的 await 可以继续执行
1 2 3 4 5 6 7 async function promiseQueue() { await Promise.reject("fail").catch(e => console.log(e)); await Promise.resolve("success").then(value => { console.log(value); }); } promiseQueue();
也可以使用 try...catch 特性忽略不必要的错误
1 2 3 4 5 6 7 8 9 async function promiseQueue() { try { await Promise.reject("fail"); } catch (error) {} await Promise.resolve("success").then(value => { console.log(value); }); } promiseQueue();
也可以将多个 await 放在 try…catch 中统一处理错误
1 2 3 4 5 6 7 8 9 10 11 async function get(type) { const host = "http://localhost:8083/" try { const goods = await ajax(`${host}/goodsList?type=${type}`); const category = await ajax(`${host}/category?id=${goods.id}`); console.log(lessons); } catch (error) { console.log("商品不存在"); } } get("new");
并发执行 有时需要多个await 同时执行,有以下几种方法处理,下面多个await 将产生等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 async function p1() { return new Promise(resolve => { setTimeout(() => { console.log("Ashuntefannao"); resolve(); }, 2000); }); } async function p2() { return new Promise(resolve => { setTimeout(() => { console.log("Ashun"); resolve(); }, 2000); }); } async function promises() { await p1(); await p2(); } promises();
使用 Promise.all() 处理多个promise并行执行
1 2 3 4 5 …… async function promises() { await Promise.all([p1(), p2()]); } promises();
先执行返回promise,再使用await处理结果
1 2 3 4 5 6 7 async function promises() { let a1 = p1(); let a2 = p2(); await a1; await a2; } promises();