异步promise、Async/await介绍
promise基本介绍
- Promise 是异步编程的一种解决方案,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,有以下特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
- 最要用于解决异步的层层嵌套,防止嵌套黑洞,是代码结构更简洁、提高可读性
promise 应用
- 最基本的用法
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
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 error
console.log(msg.toLowerCase());
})
.done(null, function() {
// If an exception is caused here, it will be thrown globally
});
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'); // 不会执行
}
使用注意点
- 陷阱:在使用异步方式时,忘记使用await
async function getFirstUser() {
try {
let users = await getUsers();
return users[0].name;
} catch (err) {
return {
name: 'default user'
};
}
}
// 注意添加 await
let 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);
}
}
// 并发执行可作如此修改,写法1
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 写法2
async 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
- 我想做一个披萨
- 独立制作面团
- 独立制作酱料
- 在我们决定什么样的奶酪放进披萨时,我们希望能够品尝酱汁,使披萨味道最美味,
// 示例1
async 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- 我们实际缓存的内容是方法返回的诺言,而不是最终值。这意味着我们甚至不需要等待异步方法返回任何内容,然后再缓存它的未来值。
思考点
- Async/await可以解决回调黑洞的问题,给开发、和层序的阅读性带来便捷。但是暴力、滥用Async/await同样也会带来性能问题,并发关系的被写成继发关系,就会大大影响性能。
- 要做好异常的处理工作。