idea异步编码
JavaScript中Promises的首次亮相点燃了互联网的热潮-他们帮助开发人员摆脱了回调困境,并解决了困扰遍及JavaScript程序员的异步代码的许多问题。 但是,承诺远非完美无缺。 它们仍然需要回调,在复杂的情况下仍然会很混乱,并且非常冗长。
2017年3月22日 :本文已更新,以反映对规范的更改以及当前的运行时支持。
随着ES6的出现(从此以后称为ES2015),它不仅实现了语言的原生承诺,而且不需要众多可用的库之一,我们也有了生成器 。 生成器具有暂停函数执行的能力,这意味着通过将它们包装在实用函数中 ,我们可以等待异步操作完成,然后再继续执行下一行代码。 突然,您的异步代码可能开始看起来是同步的!
但这只是第一步。 异步功能将作为今年的ES2017规范的一部分进行标准化,并且本机支持已经在增长。 异步函数采用将生成器用于异步编程的想法,并赋予它们自己的简单语义语义。 因此,您不必使用库来获取该包装实用程序函数,因为该函数是在后台处理的。
要运行本文中的异步/等待代码示例,您将需要兼容的浏览器。
运行时兼容性
在客户端,Chrome,Firefox和Opera现在支持开箱即用的异步功能。
从7.6版开始,Node.js还默认情况下启用了异步/等待功能。
异步函数与生成器
这是使用生成器进行异步编程的示例。 它使用Q库:
var doAsyncOp = Q.async(function* () {
var val = yield asynchronousOperation();
console.log(val);
return val;
});
Q.async
是包装器函数,用于处理后台的所有事情。 *
表示该函数是生成器函数, yield
是您暂停该函数并让包装函数接管的方式。 Q.async
将返回一个您可以分配的函数(就像我已经做过的那样),以分配给doAsyncOp
并随后调用。
这是使用ES7中包含的新语法摆脱混乱的样子:
async function doAsyncOp () {
var val = await asynchronousOperation();
console.log(val);
return val;
};
没什么大不了,但是我们删除了包装函数和星号,并用async
关键字替换了它们。 yield
关键字也被await
代替。 这两个示例将做完全相同的事情:等待asynchronousOperation
操作完成,然后将其值分配给val
,记录它并返回它。
将承诺转换为异步功能
如果我们使用香草承诺,那么前面的示例会是什么样?
function doAsyncOp () {
return asynchronousOperation().then(function(val) {
console.log(val);
return val;
});
};
它具有相同的行数,但是由于有很多额外的代码, then
还有回调函数传递给它。 另一个麻烦是return
关键字的重复。 一直以来,这一直困扰着我,因为它使得很难准确地确定使用诺言的函数所返回的内容。
如您所见,此函数返回一个承诺,该承诺将满足val
的值。 猜猜是什么……生成器和异步函数示例也是如此! 每当您从这些函数之一返回值时,实际上实际上是在隐式返回解析为该值的promise。 如果您根本不返回任何内容,那么您将隐式返回一个承诺,该承诺将解析为undefined
。
连锁经营
吸引许多人的诺言的方面之一是能够链接多个异步操作而不会遇到嵌套回调的能力。 这是异步功能超越承诺的领域之一。
这就是您使用promise链接异步操作的方式(诚然,我们很傻,并且一次又一次地运行相同的asynchronousOperation
)。
function doAsyncOp() {
return asynchronousOperation()
.then(function(val) {
return asynchronousOperation(val);
})
.then(function(val) {
return asynchronousOperation(val);
})
.then(function(val) {
return asynchronousOperation(val);
});
}
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
使用异步函数,我们可以像asynchronousOperation
操作一样运行:
async function doAsyncOp () {
var val = await asynchronousOperation();
val = await asynchronousOperation(val);
val = await asynchronousOperation(val);
return await asynchronousOperation(val);
};
您甚至不需要在该return语句上使用await
关键字,因为无论哪种方式,它都将返回解析为最终值的promise。
并行操作
promise的另一个强大功能之一是能够一次运行多个异步操作,并在所有异步操作完成后继续按自己的方式进行操作。 Promise.all()
是根据ES2015规范执行此操作的方法。
这是一个例子:
function doAsyncOp() {
return Promise.all([
asynchronousOperation(),
asynchronousOperation()
]).then(function(vals) {
vals.forEach(console.log);
return vals;
});
}
异步功能也可以做到这一点,尽管您仍然需要使用Promise.all()
:
async function doAsyncOp() {
var vals = await Promise.all([
asynchronousOperation(),
asynchronousOperation()
]);
vals.forEach(console.log.bind(console));
return vals;
}
即使有Promise.all
,它仍然干净得多。
处理拒绝
承诺具有解决或拒绝的能力。 可以通过传递给then
的第二个函数或catch
方法来处理被拒绝的承诺。 由于我们没有使用任何Promise
API方法,我们将如何处理拒绝? 我们有这样做try
和catch
。 使用异步函数时,拒绝会作为错误传递,这使它们可以使用内置JavaScript错误处理代码进行处理。
function doAsyncOp() {
return asynchronousOperation()
.then(function(val) {
return asynchronousOperation(val);
})
.then(function(val) {
return asynchronousOperation(val);
})
.catch(function(err) {
console.error(err);
});
}
这与我们的链接示例非常相似,只不过我们用catch
替换了最终的链接调用。 这就是异步函数的样子。
async function doAsyncOp () {
try {
var val = await asynchronousOperation();
val = await asynchronousOperation(val);
return await asynchronousOperation(val);
} catch (err) {
console.err(err);
}
};
这不是因为简洁的其他转换到异步功能,但它正是你将如何与同步代码做到这一点。 如果您在此处未捕获到错误,它将一直冒泡,直到被调用程序函数捕获,否则就不会被捕获,并且您将因运行时错误而终止执行。 承诺的工作方式相同,不同之处在于拒绝不需要是错误的; 它们只是解释错误原因的字符串。 如果未捕获到因错误而创建的拒绝,则将看到运行时错误,但是如果仅使用字符串,则它将以静默方式失败。
破碎的承诺
要拒绝本机承诺,可以在Promise
构造函数内使用reject
,也可以在Promise
构造函数内或then
或catch
回调中引发错误。 如果将错误抛出该范围之外,则该错误将不会包含在承诺中。
以下是一些拒绝承诺的方法示例:
function doAsyncOp() {
return new Promise(function(resolve, reject) {
if (somethingIsBad) {
reject("something is bad");
}
resolve("nothing is bad");
});
}
/*-- or --*/
function doAsyncOp() {
return new Promise(function(resolve, reject) {
if (somethingIsBad) {
reject(new Error("something is bad"));
}
resolve("nothing is bad");
});
}
/*-- or --*/
function doAsyncOp() {
return new Promise(function(resolve, reject) {
if (somethingIsBad) {
throw new Error("something is bad");
}
resolve("nothing is bad");
});
}
通常,最好尽可能使用new Error
因为它将包含有关错误的其他信息,例如引发错误的行号以及可能有用的堆栈跟踪。
以下是一些示例,其中不会因诺言而捕获错误:
function doAsyncOp() {
// the next line will kill execution
throw new Error("something is bad");
return new Promise(function(resolve, reject) {
if (somethingIsBad) {
throw new Error("something is bad");
}
resolve("nothing is bad");
});
}
// assume `doAsyncOp` does not have the killing error
function x() {
var val = doAsyncOp().then(function() {
// this one will work just fine
throw new Error("I just think an error should be here");
});
// this one will kill execution
throw new Error("The more errors, the merrier");
return val;
}
使用异步功能时,承诺会因抛出错误而被拒绝。 范围问题不会出现-您可以在异步函数内的任何地方引发错误,并且会被promise捕获:
async function doAsyncOp() {
// the next line is fine
throw new Error("something is bad");
if (somethingIsBad) {
// this one is good too
throw new Error("something is bad");
}
return "nothing is bad";
}
// assume `doAsyncOp` does not have the killing error
async function x() {
var val = await doAsyncOp();
// this one will work just fine
throw new Error("I just think an error should be here");
return val;
}
当然,我们永远都不会到第二个错误或到return
内部doAsyncOp
功能,因为该错误将被抛出,并在该函数内停止执行。
陷阱
如果您不熟悉异步功能,则需要注意的一件事是使用嵌套功能。 例如,如果您的异步函数中有另一个函数(通常作为对某个东西的回调),您可能会认为您可以仅在该函数中使用await
。 你不能 您只能在async
函数中直接使用await
。
例如,这不起作用:
async function getAllFiles(fileNames) {
return Promise.all(
fileNames.map(function(fileName) {
var file = await getFileAsync(fileName);
return parse(file);
})
);
}
第4行的await
无效,因为它在常规函数中使用。 而是,回调函数必须附加有async
关键字。
async function getAllFiles(fileNames) {
return Promise.all(
fileNames.map(async function(fileName) {
var file = await getFileAsync(fileName);
return parse(file);
})
);
}
当您看到它时很明显,但是仍然需要注意这一点。
如果您想知道,以下是使用promise的等效方法:
function getAllFiles(fileNames) {
return Promise.all(
fileNames.map(function(fileName) {
return getFileAsync(fileName).then(function(file) {
return parse(file);
});
})
);
}
下一个难题涉及人们认为异步功能是同步功能的问题。 请记住,异步函数将运行,如果它是同步里面的代码,但它仍然会立即返回一个承诺,并允许同时致力于履行其它代码,它外面执行。 例如:
var a = doAsyncOp(); // one of the working ones from earlier
console.log(a);
a.then(function() {
console.log("`a` finished");
});
console.log("hello");
/* -- will output -- */
Promise Object
hello
`a` finished
您可以看到异步功能仍然使用内置的Promise,但是它们是在后台使用的。 尽管其他人可以使用常规的Promise API或使用自己的异步函数来调用我们的异步函数,这使我们能够在异步函数中进行同步思考。
更好的异步代码,今天!
即使您不能在本地使用它,也可以编写它并使用工具将其编译为ES5。 异步功能就是使您的代码更具可读性,从而更易于维护。 只要我们有源地图,我们就可以始终使用更清洁的ES2017代码。
有几种工具可以将异步功能(和其他ES2015 +功能)编译为ES5代码。 如果您使用的是Babel ,这只是安装ES2017预设的一种情况。
您是否已经在利用异步功能为我们带来的强大功能? 这是您今天考虑使用的东西吗? 让我们在评论中知道。
翻译自: https://www.sitepoint.com/simplifying-asynchronous-coding-async-functions/
idea异步编码