Promises在JavaScript刚发布的时候就开始火爆网络——它们将开发者从回调地狱中解放出来,解决了很多地方困扰JavaScript开发者的异步问题。但承诺远非保证。他们一直要求回电,并且在一些复杂的问题上仍然是混乱和令人难以置信的冗余。随着ES6(现在叫ES2015)的到来,除了引入Promise的规范,不需要那些无数的库,我们还有生成器。生成器可以在函数内部停止执行,这意味着通过将它们包装在一个多用途函数中,我们可以在代码移至下一行之前等待异步操作完成。突然之间,您的异步代码可能开始看起来是同步的。这只是第一步。今年加入ES2017,异步功能已经标准化,本地支持进一步优化。异步函数的思想是使用生成器进行异步编程,并赋予它们自己的语义和语法。因此,您不需要使用库来获取包装的实用函数,因为这些都是在幕后处理的。要运行本文中的异步/等待示例,您需要一个兼容的浏览器。在客户端,Chrome、Firefox和Opera都很好地支持异步功能。从7.6版本开始,Node.js默认启用了async/await。异步函数和生成器的比较下面是一个使用生成器进行异步编程的例子,使用Q库:Q.async是一个包装函数,处理场景之后的事情。其中*表示充当生成器函数,而yield表示停止该函数并用包装函数替换它。Q.async将返回一个您可以分配给的函数,就像doAsyncOp一样,稍后调用。ES7中的新语法更加简洁,操作示例如下:asyncfunctiondoAsyncOp(){varval=awaitasynchronousOperation();console.log(val);returnval;};区别不大,去掉了一个wrappedfunction和*符号,并改为使用异步关键字。yield关键字也被await取代。这两个例子其实做的是同一件事:asynchronousOperation完成后,赋值给val,然后输出并返回结果。将Promises变成异步函数如果我们使用VanillaPromises,前面的例子会是什么样子?functiondoAsyncOp(){returnasynchronousOperation().then(function(val){console.log(val);returnval;});};这里我们有相同数量的代码行,但那是因为then和传递给它的回调函数添加了很多额外的代码。另一个烦恼是两个return关键字。这一直困扰着我,因为很难弄清楚使用promises的函数究竟返回什么。如您所见,此函数返回将分配给val的承诺,猜猜生成器和异步函数示例的作用!无论您在此函数中返回什么,您都在隐式返回解析为该值的承诺。如果您根本不返回任何值,则您隐式返回的承诺将解析为未定义。Promise在链式操作中如此受欢迎的原因之一是它可以将多个异步操作以链式方式连接起来,避免了嵌入式回调。但是异步函数比Promises做得更好。下面演示如何使用Promise进行链式操作(我们简单地多次运行asynchronousOperation来演示)。functiondoAsyncOp(){returnasynchronousOperation().then(函数(val){returnasynchronousOperation(val);}).then(函数(val){returnasynchronousOperation(val);}).then(function(val){returnasynchronousOperation(val);});}使用async函数,只需要像写同步代码一样调用asynchronousOperation:asyncfunctiondoAsyncOp(){varval=awaitasynchronousOperation();val=awaitasynchronousOperation(val);val=awaitasynchronousOperation(val);returnawaitasynchronousOperation(val);};甚至不需要在***的return语句中使用await,因为无论有没有它,它都会返回一个包含可处理最终值的Promise。并发操作Promises还有一个很棒的特性,它们可以同时执行多个异步操作,并等待它们全部完成,然后再进行其他事件。ES2015规范中提供了Promise.all(),就是用来做这个的。下面是一个例子:也可以用作异步函数:asyncfunctiondoAsyncOp(){varvals=awaitPromise.all([asynchronousOperation(),asynchronousOperation()]);vals.forEach(console.log.bind(console));returnvals;}这里使用添加了Promise.all,代码还是很清晰的。处理拒绝承诺可以被接受(resovled)或拒绝(rejected)。被拒绝的Promises可以由作为第二个参数传递给then的函数处理,或者传递给catch方法。既然我们不使用PromiseAPI中的方法,我们应该如何处理拒绝?可以通过tryandcatch来处理。使用异步函数时,拒绝作为错误传递,以便它们可以由JavaScript本机支持的错误处理代码处理。functiondoAsyncOp(){returnasynchronousOperation().then(函数(val){returnasynchronousOperation(val);}).then(函数(val){returnasynchronousOperation(val);}).catch(函数(err){console.error(err);});}这与我们的链接示例非常相似,只是最后一个链接已更改为调用catch。如果用异步函数编写,它看起来像这样。asyncfunctiondoAsyncOp(){try{varval=awaitasynchronousOperation();val=awaitasynchronousOperation(val);returnawaitasynchronousOperation(val);}catch(err){console.err(err);}};它不像其他转换成异步函数那样简洁,但是真的和写同步代码一样。如果你不在这里捕获错误,它将被抛出调用链,直到它被捕获到某个地方。如果它仍未被捕获,它最终将中止程序并抛出运行时错误。Promises的工作方式相同,只是拒绝不必作为错误处理;它们可能只是一个说明错误情况的字符串。如果您没有捕获作为错误创建的拒绝,您将看到一个运行时错误,但如果您只使用一个字符串,它将失败并且没有输出。要中断一个Promise来拒绝一个原生的Promise,你只需要在Promise的构造函数中使用reject即可。当然,你也可以直接抛出错误——在Promise构造函数中,在then或catch的回调中抛出即可。如果错误抛到别处,Promise无法控制。下面是一些拒绝Promise的例子:--*/functiondoAsyncOp(){returnnewPromise(function(resolve,reject){if(somethingIsBad){reject(newError("somethingisbad"));}resolve("nothingisbad");});}/*--或者---*/functiondoAsyncOp(){returnnewPromise(function(resolve,reject){if(somethingIsBad){thrownewError("somethingisbad");}resolve("nothingisbad");});}一般来说,***使用新的错误,因为它将包含有关错误的其他信息,例如抛出错误的行号,以及可能有用的调用堆栈。以下是一些抛出Promises无法捕获的错误的示例:functiondoAsyncOp(){//thenextlinewillkillexecutionthrownewError("somethingisbad");returnnewPromise(function(resolve,reject){if(somethingIsBad){thrownewError("somethingisbad");}resolve("nothingisbad");});}//假设`doAsyncOp`没有杀死错误函数x(){varval=doAsyncOp().then(function(){//thisonewillworkjustfinethrownewError("Ijustthinkanerrorshouldbehere");});//thisonewillkillexecutionthrownewErrors("Themothemerrier");returnval;}在异步函数的Promise中抛出错误不会在范围内造成问题——你可以在异步函数的任何地方抛出错误,它总是会被Promise捕获:asyncfunctiondoAsyncOp(){//thenextlineisfinethrownewError("somethingisbad");if(somethingIsBad){//thisoneisgoodtoothrownewError("somethingisbad");}return"nothingisbad";}//assume`doAsyncOp`doesnothavethekillingerrorasyncfunctionx(){varval=awaitdoAsyncOp();//thisonewillworkjustfinethrownewError("我只是觉得一个errorshouldbehere");returnval;}当然我们不会跑到doAsyncOp的第二个错误,也不会跑到return语句,因为之前抛出的错误已经中止了函数。async函数,需要注意嵌套的问题函数。比如你的async函数中有另外一个函数(一般是callback),你可能认为可以在里面使用await,其实不行。你只能直接在async函数中使用await。例如,此代码不起作用:asyncfunctiongetAllFiles(fileNames){returnPromise.all(fileNames.map(function(fileName){varfile=awaitgetFileAsync(fileName);returnparse(file);}));}第4行await无效,因为它是在一个普通的函数中使用。但是,这个问题可以通过在回调函数中添加async关键字来解决。file);}));}你看了会觉得理所当然,ev那么,这种情况还是要小心的。也许您还想知道使用Promise的等效代码:}));}下一个问题是将异步函数视为同步函数。需要记住的是async函数里面的代码是同步运行的,但是会立马返回一个Promise继续运行外面的代码,比如:vara=doAsyncOp();//oneoftheworkingonesfromearlierconsole.log(a);a.then(function(){console.log("`a`finished");});console.log("hello");/*--willoutput--*/PromiseObjecthello`a`完成你会看到async函数实际上使用了内置的Promise。这让我们开始思考异步函数中的同步行为,其他人可以通过普通的PromiseAPI调用我们的异步函数,或者使用他们自己的异步函数。这些天更好的异步代码!即使你不能原生使用异步代码,你也可以编写它或使用工具将其编译为ES5。异步函数使代码更易于阅读和维护。只要我们有sourcemaps,我们总是可以使用更干净的ES2017代码。有许多工具可以将异步函数(和其他ES2015+函数)编译为ES5代码。如果您使用的是Babel,这只是安装ES2017预设的示例。
