异步promise、Async/await介绍
promise基本介绍
Promise 是异步编程的一种解决方案,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,有以下特点:对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。最要用于解决异步的层层嵌套,防止嵌套黑洞,是代码结构更简洁、提高可读性promise 应用
最基本的用法const promise = new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}});复制代码
promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,它们是两个函数。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”,reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected)实例生成后可以这样使用
promise.then(function(value) {// success}, function(error) {// failure});复制代码
2、延迟操作
function delay(time) {return new Promise(function(resolve, reject){setTimeout(resolve, time);});}delay(1000).then(function(){console.log("after 1000ms");return delay(2000);}).then(function(){console.log("after another 2000ms");}).then(function(){console.log("step 4 (next Job)");return delay(5000);})// ...复制代码
图片加载
function loadImageAsync(url) {return new Promise(function(resolve, reject) {const image = new Image();image.onload = function() {resolve(image);};image.onerror = function() {reject(new Error('Could not load image at ' + url));};image.src = url;});}复制代码
简易版封装数据获取
const getJSON = function(url) {const promise = new Promise(function(resolve, reject){const handler = function() {if (this.readyState !== 4) {return;}if (this.status === 200) {resolve(this.response);} else {reject(new Error(this.statusText));}};const client = new XMLHttpRequest();client.open("GET", url);client.onreadystatechange = handler;client.responseType = "json";client.setRequestHeader("Accept", "application/json");client.send();});return promise;};getJSON("/posts.json").then(function(json) {console.log('Contents: ' + json);}, function(error) {console.error('出错了', error);});复制代码
示例5:每10秒检测一次用户是否登录
function onUserLoggedIn(id) {return ajax(`user/${id}`).then(user => {if (user.state === 'logged_in') {return;}return new Promise(resolve => {return setTimeout(resolve, 10 * 1000));}).then(() => {return onUserLoggedIn(id);})});}复制代码
示例5改进:业务代码与promise层的代码混合在一起,可读性差,继续改进
function delay(time) {return new Promise(resolve => {return setTimeout(resolve, time));});}function until(conditionFn, delayTime = 1000) {return Promise.resolve().then(() => {return conditionFn();}).then(result => {if (!result) {return delay(delayTime).then(() => {return until(conditionFn, delayTime);});}});}复制代码
示例5箭头函数改进
let delay = time =>new Promise(resolve =>setTimeout(resolve, time));let until = (cond, time) =>cond().then(result =>result || delay(time).then(() =>until(cond, time)));复制代码
使用示例5
// 写法一function onUserLoggedIn(id) {return until(() => {return ajax(`user/${id}`).then(user => {return user.state === 'logged_in';});}, 10 * 1000);}// 写法二async function onUserLoggedIn(id) {return await until(async () => {let user = await ajax(`user/${id}`);return user.state === 'logged_in';}, 10 * 1000);}复制代码
callback回调异步转换
function callbackToPromise(method, ...args) {return new Promise(function(resolve, reject) {return method(...args, function(err, result) {return err ? reject(err) : resolve(result);});});}async function getFirstUser() {let users = await callbackToPromise(getUsers);return users[0].name;}复制代码
异常处理
var p = Promise.resolve(374);p.then(function fulfilled(msg){// numbers don't have string functions,// so will throw an errorconsole.log(msg.toLowerCase());}).done(null, function() {// If an exception is caused here, it will be thrown globally });复制代码
promise 实现的参考
Async/await
Async/await 基本介绍
async函数是对Generator 函数的改进基本用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。async函数内部return语句返回的值,会成为then方法回调函数的参数。async function getStockPriceByName(name) {const symbol = await getStockSymbol(name);const stockPrice = await getStockPrice(symbol);return stockPrice;}getStockPriceByName('goog').then(function (result) {console.log(result);});复制代码
async 函数有多种使用形式。
// 函数声明async function foo() {}// 函数表达式const foo = async function () {};// 对象的方法let obj = { async foo() {} };obj.foo().then(...)// Class 的方法class Storage {constructor() {this.cachePromise = caches.open('avatars');}async getAvatar(name) {const cache = await this.cachePromise;return cache.match(`/avatars/${name}.jpg`);}}const storage = new Storage();storage.getAvatar('jake').then(…);// 箭头函数const foo = async () => {};复制代码
await 命令await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
async function f() {return await 123;}f().then(v => console.log(v))复制代码
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行
async function f() {await Promise.reject('出错了');await Promise.resolve('hello world'); // 不会执行}复制代码
使用注意点
陷阱:在使用异步方式时,忘记使用awaitasync function getFirstUser() {try {let users = await getUsers();return users[0].name;} catch (err) {return {name: 'default user'};}}// 注意添加 awaitlet user = await getFirstUser();复制代码
陷阱:不存在继发关系同时使用多个await
let foo = await getFoo();let bar = await getBar();// 应改为如下所示的方法,并行运行等待结果,//性能上有优势let [foo, bar] = await Promise.all([getFoo(), getBar()]);// 第二种方式,不推荐这种方法,可读性差let fooPromise = getFoo();let barPromise = getBar();let foo = await fooPromise;let bar = await barPromise;复制代码
陷阱:await命令只能用在async函数之中,如果用在普通函数,就会报错
async function dbFuc(db) {let docs = [{}, {}, {}];// 报错docs.forEach(function (doc) {await db.post(doc);});}// 改进这是继发执行async function dbFuc(db) {let docs = [{}, {}, {}];for (let doc of docs) {await db.post(doc);}}// 并发执行可作如此修改,写法1async function dbFuc(db) {let docs = [{}, {}, {}];let promises = docs.map((doc) => db.post(doc));let results = await Promise.all(promises);console.log(results);}// 写法2async function dbFuc(db) {let docs = [{}, {}, {}];let promises = docs.map((doc) => db.post(doc));let results = [];for (let promise of promises) {results.push(await promise);}console.log(results);}复制代码
处理异常
myApp.registerEndpoint('GET', '/api/firstUser', async function(req, res) {try {let firstUser = await getFirstUser();res.json(firstUser)} catch (err) {console.error(err);res.status(500);}});复制代码
promise与Asyns联合使用,解决继发、并发同存在的问题
示例1我想做一个披萨独立制作面团独立制作酱料在我们决定什么样的奶酪放进披萨时,我们希望能够品尝酱汁,使披萨味道最美味,// 示例1async function makePizza(sauceType = 'red') {let dough = await makeDough();let sauce = await makeSauce(sauceType);let cheese = await grateCheese(sauce.determineCheese());dough.add(sauce);dough.add(cheese);return dough;}复制代码
示例1运行如下
|-------- dough --------> |-------- sauce --------> |-- cheese -->制作面团--->制作酱料--->磨碎奶酪,这是最好的么?我们要等待面团完成后,才能尝酱的味道,有没有更快的方法呢?复制代码
示例改进
async function makePizza(sauceType = 'red') {let [ dough, sauce ] =await Promise.all([ makeDough(), makeSauce(sauceType) ]);let cheese = await grateCheese(sauce.determineCheese());dough.add(sauce);dough.add(cheese);return dough;}复制代码
运行如下
|-------- dough -------->|--- sauce ---> |-- cheese -->- Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。- 虽然,douch与sauce并行,相较于1会更快一点磨碎奶酪,从Promise.all方法运行机制来看,但仍然要等待面、酱料制作好了,才能磨碎奶酪,那么有更好的办法么?复制代码
示例3
function makePizza(sauceType = 'red') {let doughPromise = makeDough();let saucePromise = makeSauce(sauceType);let cheesePromise = saucePromise.then(sauce => {return grateCheese(sauce.determineCheese());});return Promise.all([ doughPromise, saucePromise, cheesePromise ]).then(([ dough, sauce, cheese ]) => {dough.add(sauce);dough.add(cheese);return dough;});}// or 用asyns/await改写async function makePizza(sauceType = 'red') {let doughPromise = makeDough();let saucePromise = makeSauce(sauceType);let sauce = await saucePromise;let cheese = await grateCheese(sauce.determineCheese());let dough = await doughPromise;dough.add(sauce);dough.add(cheese);return dough;}复制代码
运行结果如下
|--------- dough --------->|---- sauce ----> |-- cheese -->从代码阅读来看,这不是最好的写法,继续改进复制代码
示例1 终版跟记忆函数的结合
function memoize(method) {let cache = {};return async function() {let args = JSON.stringify(arguments);cache[args] = cache[args] || method.apply(this, arguments);return cache[args];};}async function makePizza(sauceType = 'red') {let prepareDough = memoize(async () => makeDough());let prepareSauce = memoize(async () => makeSauce(sauceType));let prepareCheese = memoize(async () => {return grateCheese((await prepareSauce()).determineCheese());});let [ dough, sauce, cheese ] = await Promise.all([prepareDough(), prepareSauce(), prepareCheese()]);dough.add(sauce);dough.add(cheese);return dough;}复制代码
示例二: 制作汤
async function getSoupRecipe(<soupType>)async function hireSoupChef(<soupRecipe:requiredSkills>)async function buySoupPan()async function makeSoup(<soupChef>, <soupRecipe>, <soupPan>)- 提供一些代码制作我最喜欢的汤- 制作汤需要配方、厨师、锅- 上面的代码对于一些方法来说是一个松散的界面,它可以让你得到你需要的东西来制作汤复制代码
基本方法
async function getSoupRecipe(soupType) {return await http.get(`/api/soup/${soupType}`);}async function buySoupPan() {return await http.get(`/api/soupPan`);}async function hireSoupChef(requiredSkills) {return await http.post(`/api/soupChef/hire`, {requiredSkills: requiredSkills});}async function makeSoup(soupChef, soupRecipe, soupPan) {return await http.post(`api/makeSoup`, {soupRecipe, soupPan, soupChef});}复制代码
示例2:暴力写法
async function makeSoupFromType(soupType) {let soupRecipe = await getSoupRecipe(soupType);let soupPan = await buySoupPan();let soupChef = await hireSoupChef(soupRecipe.requiredSkills);return await makeSoup(soupChef, soupRecipe, soupPan);}复制代码
代码是可以工作的,但是是继发执行的,买锅和聘请厨师并没有前后关系,这就会导致效率的低下,煮汤的时间延长,会饿肚子的。如何改进成将有继发关系的让他一步一步执行,没有继发关系的,并发执行,以提升效率。
示例2改进型
async function makeSoupFromType(soupType) {let [soupRecipe, soupPan] = await* [getSoupRecipe(soupType),buySoupPan()];let soupChef = await hireSoupChef(soupRecipe.requiredSkills);return await makeSoup(soupChef, soupRecipe, soupPan);}复制代码
await* 只是一种表示方法,在真实的代码中应该用Promise.all()这时代码发生了耦合,明确指出'这两种方法必须并行运行'和'此后必须运行,维护性较差聘请厨师还是需要有锅,不合逻辑。继续改进示例2
async function makeSoupFromType(soupType) {let soupRecipePromise = getSoupRecipe(soupType);async function hireSoupChefWithSoupRecipe(_soupRecipePromise) {let soupRecipe = await _soupRecipePromise;return await hireSoupChef(soupRecipe.requiredSkills);}let [ soupRecipe, soupPan, soupChef ] = await* [soupRecipePromise,buySoupPan(),hireSoupChefWithSoupRecipe(soupRecipePromise)]);return await makeSoup(soupChef, soupRecipe, soupPan);}复制代码
汤料要获取两次,并提前缓存汤料
不方便阅读理解,在以后的维护、改进过程中,如果在 getSoupRecipe(soupType)加上await又会变慢
示例2:继续改进,看看缓存函数和asyns能发生什么化学反应
考虑我们在上述方法中遇到的问题。我们需要得到一份汤配方,但我们不想两次拿取,所以我们必须自己手动缓存。
function memoize(method) {let cache = {};return async function() {let args = JSON.stringify(arguments);cache[args] = cache[args] || method.apply(this, arguments);return cache[args];};}let getSoupRecipe = memoize(async function(soupType) {return await http.get(`/api/soup/${soupType}`);});let buySoupPan = memoize(async function() {return await http.get(`/api/soupPan`);});let hireSoupChef = memoize(async function(soupType) {let soupRecipe = await getSoupRecipe(soupType)return await http.post(`/api/soupChef/hire`, {requiredSkills: soupRecipe.requiredSkills});});let makeSoup = memoize(async function(soupType) {let [ soupRecipe, soupPan, soupChef ] = await* [getSoupRecipe(soupType), buySoupPan(), hireSoupChef(soupType)];return await http.post(`api/makeSoup`, {soupRecipe, soupPan, soupChef});});复制代码
memoize()把任何异步函数变成一个memoized函数 - 也就是说,如果它被调用了两次相同的参数,它将在第二次返回相同的缓存值。memoize函数中不使用await
- 我们实际缓存的内容是方法返回的诺言,而不是最终值。这意味着我们甚至不需要等待异步方法返回任何内容,然后再缓存它的未来值。