[转] Understanding JavaScript’s async await

PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果

Promise<Action>这样的对象呢,异步调用后的结果是一个Action,传到处理函数里

async/await的作用是,不需要写then函数了,相当于与自动帮你写,你只需要把异步调用后的结果保存下来就可以了

https://ponyfoo.com/articles/understanding-javascript-async-await

http://liubin.org/promises-book/#what-is-promise

The async / await feature didn’t make the cut for ES2016, but that doesn’t mean it won’t be coming to JavaScript. At the time of this writing, it’s a Stage 3 proposal, and actively being worked on. The feature is already in Edge, and should it land in another browser it’ll reach Stage 4 – paving its way for inclusion in the next Edition of the language (see also: TC39 Process).

We’ve heard about this feature for a while, but let’s drill down into it and see how it works. To be able to grasp the contents of this article, you’ll need a solid understanding of promises and generators. These resources should help you out.

Using Promises

Let’s suppose we had code like the following. Here I’m wrapping an HTTP request in a Promise. The promise fulfills with the body when successful, and is rejected with an err reason otherwise. It pulls the HTML for a random article from this blog every time.

var request = require(‘request‘);

function getRandomPonyFooArticle () {
  return new Promise((resolve, reject) => {
    request(‘https://ponyfoo.com/articles/random‘, (err, res, body) => {
      if (err) {
        reject(err); return;
      }
      resolve(body);
    });
  });
}

Typical usage of the promised code shown above is below. There, we build a promise chain transforming the HTML page into Markdown of a subset of its DOM, and then into Terminal-friendly output, to finally print it using console.log. Always remember to add .catch handlers to your promises.

var hget = require(‘hget‘);
var marked = require(‘marked‘);
var Term = require(‘marked-terminal‘);

printRandomArticle();

function printRandomArticle () {
  getRandomPonyFooArticle()
    .then(html => hget(html, {
      markdown: true,
      root: ‘main‘,
      ignore: ‘.at-subscribe,.mm-comments,.de-sidebar‘
    }))
    .then(md => marked(md, {
      renderer: new Term()
    }))
    .then(txt => console.log(txt))
    .catch(reason => console.error(reason));
}

When ran, that snippet of code produces output as shown in the following screenshot.

That code was “better than using callbacks”, when it comes to how sequential it feels to read the code.

Using Generators

We’ve already explored generators as a way of making the html available in a synthetic “synchronous” manner in the past. Even though the code is now somewhat synchronous, there’s quite a bit of wrapping involved, and generators may not be the most straightforward way of accomplishing the results that we want, so we might end up sticking to Promises anyways.

function getRandomPonyFooArticle (gen) {
  var g = gen();
  request(‘https://ponyfoo.com/articles/random‘, (err, res, body) => {
    if (err) {
      g.throw(err); return;
    }
    g.next(body);
  });
}

getRandomPonyFooArticle(function* printRandomArticle () {
  var html = yield;
  var md = hget(html, {
    markdown: true,
    root: ‘main‘,
    ignore: ‘.at-subscribe,.mm-comments,.de-sidebar‘
  });
  var txt = marked(md, {
    renderer: new Term()
  });
  console.log(txt);
});

Keep in mind you should wrap the yield call in a try / catch block to preserve the error handling we had added when using promises.

Needless to say, using generators like this doesn’t scale well. Besides involving an unintuitive syntax into the mix, your iterator code will be highly coupled to the generator function that’s being consumed. That means you’ll have to change it often as you add new await expressions to the generator. A better alternative is to use the upcoming Async Function.

Using async / await

When Async Functions finally hit the road, we’ll be able to take our Promise-based implementation and have it take advantage of the synchronous-looking generator style. Another benefit in this approach is that you won’t have to change getRandomPonyFooArticle at all, as long as it returns a promise, it can be awaited.

Note that await may only be used in functions marked with the async keyword. It works similarly to generators, suspending execution in your context until the promise settles. If the awaited expression isn’t a promise, its casted into a promise.

read();

async function read () {
  var html = await getRandomPonyFooArticle();
  var md = hget(html, {
    markdown: true,
    root: ‘main‘,
    ignore: ‘.at-subscribe,.mm-comments,.de-sidebar‘
  });
  var txt = marked(md, {
    renderer: new Term()
  });
  console.log(txt);
}

Again, – and just like with generators – keep in mind that you should wrap await in try / catch so that you can capture and handle errors in awaited promises from within the async function.

Furthermore, an Async Function always returns a Promise. That promise is rejected in the case of uncaught exceptions, and it’s otherwise resolved to the return value of the async function. This enables us to invoke an async function and mix that with regular promise-based continuation as well. The following example shows how the two may be combined (see Babel REPL).

async function asyncFun () {
  var value = await Promise
    .resolve(1)
    .then(x => x * 3)
    .then(x => x + 5)
    .then(x => x / 2);
  return value;
}
asyncFun().then(x => console.log(`x: ${x}`));
// <- ‘x: 4‘

Going back to the previous example, that’d mean we could return txt from our async read function, and allow consumers to do continuation using promises or yet another Async Function. That way, your read function becomes only concerned with pulling terminal-readable Markdown from a random article on Pony Foo.

async function read () {
  var html = await getRandomPonyFooArticle();
  var md = hget(html, {
    markdown: true,
    root: ‘main‘,
    ignore: ‘.at-subscribe,.mm-comments,.de-sidebar‘
  });
  var txt = marked(md, {
    renderer: new Term()
  });
  return txt;
}

Then, you could further await read() in another Async Function.

async function write () {
  var txt = await read();
  console.log(txt);
}

Or you could just use promises for further continuation.

read().then(txt => console.log(txt));

Fork in the Road

In asynchronous code flows, it is commonplace to execute two or more tasks concurrently. While Async Functions make it easier to write asynchronous code, they also lend themselves to code that is serial. That is to say: code that executes one operation at a time. A function with multiple await expressions in it will be suspended once at a time on each await expression until that Promise is settled, before unsuspending execution and moving onto the next await expression – not unlike the case we observe with generators and yield.

To work around that you can use Promise.all to create a single promise that you can await on. Of course, the biggest problem is getting in the habit of using Promise.all instead of leaving everything to run in a series, as it’ll otherwise make a dent in your code’s performance.

The following example shows how you could await on three different promises that could be resolved concurrently. Given that await suspends your async function and the await Promise.all expression ultimately resolves into a results array, we can use destructuring to pull individual results out of that array.

async function concurrent () {
  var [r1, r2, r3] = await Promise.all([p1, p2, p3]);
}

At some point, there was an await* alternative to the piece of code above, where you didn’t have to wrap your promises with Promise.all. Babel 5 still supports it, but it was dropped from the spec (and from Babel 6) – because reasons.

async function concurrent () {
  var [r1, r2, r3] = await* [p1, p2, p3];
}

You could still do something like all = Promise.all.bind(Promise) to obtain a terse alternative to using Promise.all. An upside of this is that you could do the same for Promise.race, which didn’t have an equivalent to await*.

const all = Promise.all.bind(Promise);
async function concurrent () {
  var [r1, r2, r3] = await all([p1, p2, p3]);
}

Error Handling

Note that errors are swallowed "silently" within an async function – just like inside normal Promises. Unless we add try / catch blocks around await expressions, uncaught exceptions – regardless of whether they were raised in the body of your async function or while its suspended during await – will reject the promise returned by the async function.

Naturally, this can be seen as a strength: you’re able to leverage try / catch conventions, something you were unable to do with callbacks – and somewhat able to with Promises. In this sense, Async Functions are akin to generators, where you’re also able to leverage try / catch thanks to function execution suspension turning asynchronous flows into synchronous code.

Furthermore, you’re able to catch these exceptions from outside the async function, simply by adding a .catch clause to the promise they return. While this is a flexible way of combining the try / catch error handling flavor with .catch clauses in Promises, it can also lead to confusion and ultimately cause to errors going unhandled.

read()
  .then(txt => console.log(txt))
  .catch(reason => console.error(reason));

We need to be careful and educate ourselves as to the different ways in which we can notice exceptions and then handle, log, or prevent them.

Using async / await Today

One way of using Async Functions in your code today is through Babel. This involves a series of modules, but you could always come up with a module that wraps all of these in a single one if you prefer that. I included npm-run as a helpful way of keeping everything in locally installed packages.

npm i -g npm-run
npm i -D   browserify   babelify   babel-preset-es2015   babel-preset-stage-3   babel-runtime   babel-plugin-transform-runtime

echo ‘{
  "presets": ["es2015", "stage-3"],
  "plugins": ["transform-runtime"]
}‘ > .babelrc

The following command will compile example.js through browserify while using babelify to enable support for Async Functions. You can then pipe the script to node or save it to disk.

npm-run browserify -t babelify example.js | node

Further Reading

The specification draft for Async Functions is surprisingly short, and should make up for an interesting read if you’re keen on learning more about this upcoming feature.

I’ve pasted a piece of code below that’s meant to help you understand how async functions will work internally. Even though we can’t polyfill new keywords, its helpful in terms of understanding what goes on behind the curtains of async / await.

Namely, it should be useful to learn that Async Functions internally leverage both generators and promises.

First off, then, the following bit shows how an async function declaration could be dumbed down into a regular function that returns the result of feeding spawn with a generator function – where we’ll consider await as the syntactic equivalent for yield.

async function example (a, b, c) {
  example function body
}

function example (a, b, c) {
  return spawn(function* () {
    example function body
  }, this);
}

In spawn, a promise is wrapped around code that will step through the generator function – made out of user code – in series, forwarding values to your “generator” code (the async function’s body). In this sense, we can observe that Async Functions really are syntactic sugar on top of generators and promises, which makes it important that you understand how each of these things work in order to get a better understanding into how you can mix, match, and combine these different flavors of asynchronous code flows together.

function spawn (genF, self) {
  return new Promise(function (resolve, reject) {
    var gen = genF.call(self);
    step(() => gen.next(undefined));
    function step (nextF) {
      var next;
      try {
        next = nextF();
      } catch(e) {
        // finished with failure, reject the promise
        reject(e);
        return;
      }
      if (next.done) {
        // finished with success, resolve the promise
        resolve(next.value);
        return;
      }
      // not finished, chain off the yielded promise and `step` again
      Promise.resolve(next.value).then(
        v => step(() => gen.next(v)),
        e => step(() => gen.throw(e))
      );
    }
  });
}

The highlighted bits of code should aid you in understanding how the async / await algorithm iterates over the generator sequence (of await expressions), wrapping each item in the sequence in a promise and then chaining that with the next step in the sequence. When the sequence is over or one of the promises is rejected, the promise returned by the underlying generator function is settled.

Special thanks to @ljharb, @jaydson, @calvinf, @ericclemmons, @sherman3ero, @matthewmolnar3, and @rauschma for reviewing drafts of this article.

时间: 2024-07-29 22:13:39

[转] Understanding JavaScript’s async await的相关文章

理解 JavaScript 的 async/await

随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await.我第一次看到这组关键字并不是在 JavaScript 语言里,而是在 c# 5.0 的语法中.C# 的 async/await 需要在 .NET Framework 4.5 以上的版本中使用,因此我还很悲伤了一阵--为了要兼容 XP 系统,我们开发的软件不能使用高于 4.0 版本的 .NET Framework. 我之前在<闲谈异步调用"扁平"化> 中就谈到了这个问题.无论

【转】6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)

原文:https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9 ---------------------------------------------------------------------------------------------- 6 Reasons Why JavaScript's Async/Await Blows Prom

【前端_js】理解 JavaScript 的 async/await

async 和 await 在干什么 任意一个名称都是有意义的,先从字面意思来理解.async 是“异步”的简写,而 await 可以认为是 async wait 的简写.所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成. 理解 JavaScript 的 async/await 原文地址:https://www.cnblogs.com/leiblog/p/11057896.html

JavaScript 的 Async\/Await 完胜 Promise 的六

参考:http://www.10tiao.com/html/558/201705/2650964601/1.html Node 现在从版本 7.6 开始就支持 async/await 了. 简介: Async/await 是一种编写异步代码的新方法.之前异步代码的方案是回调和 promise. Async/await 实际上是建立在 promise 的基础上.它不能与普通回调或者 node 回调一起用. Async/await 像 promise 一样,也是非阻塞的. Async/await 让

简单理解JavaScript 的async/await

什么是Async/Await? async 函数 : 是 Generator 函数的语法糖 async函数返回一个 Promise 对象,可以使用then方法添加回调函数.当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句. async/await与Promise一样,是非阻塞的 async函数返回的是 Promise 对象,可以作为await命令的参数 二.语法 1.返回 Promise 对象 async函数返回一个 Promise 对象. asyn

JavaScript 的 async/await

随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await. 异步编程的最高境界,就是根本不用关心它是不是异步. async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案. async 和 await 起了什么作用 async 起什么作用 这个问题的关键在于,async 函数是怎么处理它的返回值的! 我们当然希望它能直接通过 return 语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了.所以,写段代码来试试,看它到

从地狱到天堂,Node 回调向 async/await 转变

Node7.6 开始正式支持 async/await,而 async/await 由于其可以以同步形式的代码书写异步程序,被喻为异步调用的天堂.然而 Node 的回调模式在已经根深蒂固,这个被喻为"回调地狱"的结构形式推动了 Promise 和 ES6 的迅速成型.然而,从地狱到天堂,并非一步之遥! async/await 基于 Promise,而不是基于回调,所以要想从回调地狱中解脱出来,首先要把回调实现修改为 Promise 实现--问题来了,Node 这么多库函数,还有更多的第三

callback vs async.js vs promise vs async / await

需求: A.依次读取 A|B|C 三个文件,如果有失败,则立即终止. B.同时读取 A|B|C 三个文件,如果有失败,则立即终止. 一.callback 需求A: let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function (callback) { if (read(1)) { return callback(null, "111");

Async/Await替代Promise的6个理由

译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码:在实践过程中,却发现Promise并不完美:技术进步是无止境的,这时,我们有了Async/Await. 原文: 6 Reasons Why JavaScript’s Async/Await Blows Promises Away 译者: Fundebug 为了保证可读性,本文采用意译而非直译. Node.js 7.6已经支持async/await了,如果你