function getUser(id, profile) { return new Promise((resolve, reject) => { User .find(id) .then((user) => { if(_.isEmpty(user)) return {}; user.profile = profile; return user; }) .then((user) => Subscription.find(user.id)) .then(subscription => { if(_.isEmpty(subscription)) { user.subscription = null; } else { user.subscription = subscription; } return resolve(user) }) .catch(err => reject(err)) }) }
上面的代码工作完全正常,但我们肯定可以使其更具可读性,简洁,易于调试与async/ await。让我们去吧。
async function getUser(id, profile) { try { const user = await User.find(id); if(_.isEmpty(user)) return {}; user.profile = profile; const subscription = await Subscription.find(user.id); user.subscription = subscription return user; } catch(err) { console.log(err); } }
回调和Async/Await是敌人正如我们在前面的示例中已经看到的那样,promise与async/一起使用非常好await。任何返回promise的函数都可以与await语句一起使用。
但是当涉及到回调时,情况恰恰相反,回调不能直接与async/一起使用await,必须将它们转换为Promise。
让我们考虑以下函数,该函数异步测试值是否为偶数(引发错误)。
function asyncEven(id, cb){ setTimeout(() => { const even = id%2 === 0; if (even) return cb(null, "even"); else return cb("not even"); }, 2000); }
我们知道在回调中不允许使用await,但是仍然尝试一下。
(async function() { //🐶👹 Wrong way const even = await asyncEven(2); console.log("isEven ", even); //undefined })();
您一定在想,我们没有附加一个回调,这就是它打印的原因undefined。
让我们附加一个回调,这是很奇怪的,但是让我们有耐心。
(async function() { //this is also wrong 🐶👹 const even = await asyncEven(2, (err, data) => { console.log("inside await on callback", err, data)}); console.log("isEven ", even); })(); /* output: even undefined inside await on callback even null */
似乎调用了回调,并且我们还从asyncEven函数中获取了值。没错,但这仍然是错误的方法。
await对回调没有影响。这类似于在同步功能上进行等待。
那为什么它返回undefined呢?这是个好问题。这是异步编程的默认性质。该setTimeout的功能是一个回调返回通过回调值2000毫秒之后,同时,控制开始执行的下一行代码,并且它达到的功能,这就是为什么我们得到的最终未定义。
那么解决方案是什么?很简单 将asyncEven功能变为承诺并await像冠军一样使用。
function asyncEven(id,) { return new Promise((resolve, reject) => { setTimeout(() => { const even = id%2 === 0; if (even) return resolve("even"); else return reject("not even"); }, 2000); }) } (async function() { // waits for the execution const even = await asyncEven(2); console.log("iseven ", even); })();
ForEach不适合与 Async/Await如果我们将ForEach循环与一起使用,则可能会有副作用async/await。考虑以下示例,console.log此处的语句不等待await
greet(name)。 async function greet(name) { return Promise.resolve(`Hello ${name}, how are you ?`); } (function() { console.log("before printing names"); const names = ['john', 'jane', 'joe']; names.forEach(async (name) => { //does not wait here console.log(await greet(name)); }); console.log("after printing names"); })(); /* before printing names after printing names Hello john, how are you ? Hello jane, how are you ? Hello joe, how are you ? */
不仅仅是语法糖到目前为止,我们只知道这async/await使我们的代码更具可读性,调试友好性,并且有人说这是javascript promise的语法糖。实际上,它不只是语法糖。
// promise async1() .then(x => asyncTwo(x)) .then(y => asyncThree(y)) //other statement console.log("hello") //async await x = await async1(); y = await asyncTwo(x); await asyncThree(y);
await暂停当前函数的执行,而promise继续执行当前函数,将值添加到中then()。这两种执行程序的方式之间存在显着差异。
让我解释一下,考虑诺言版本,如果asyncTwo()或asyncThree()在执行任务时抛出异步错误,它将包含async1()在堆栈跟踪中吗?
这里的promise不会暂停当前函数的执行,当asyncTwo解析或拒绝时,上下文不在promise语句之内。因此,理想情况下,它不能包含asyncOne在堆栈跟踪中。但是由于使用了V8引擎,它在这里做了一些神奇的工作,通过asyncOne()提前引用以便包含asyncOne()在上下文中。但这不是免费的。捕获堆栈跟踪需要花费时间(即降低性能)。存储这些堆栈跟踪需要内存。
async/await在性能方面,这是拍子承诺的地方,因为当前功能的执行将暂停,直到等待功能完成为止,因此我们已经对该功能有了参考。