Home > Web Front-end > JS Tutorial > body text

Simplify asynchronous code with Async functions (JavaScript development tips)

PHPz
Release: 2017-04-23 09:49:58
Original
1260 people have browsed it

Promise became popular on the Internet when it was first released on JavaScript - they help developers escape callback hell and solve the asynchrony problems that plague JavaScript developers in many places. But Promises are far from perfect. They keep requesting callbacks, can still be a bit messy and incredibly redundant on some complex issues.

With the arrival of ES6 (now called ES2015), in addition to the introduction of Promise specifications and no need to request countless libraries, we also have generators. Generators can stop execution inside a function, which means they can be wrapped in a multipurpose function and we can wait for the asynchronous operation to complete before moving to the next line of code. Suddenly your asynchronous code may start to look synchronous.

This is just the first step. Asynchronous functions have been standardized since they were added to ES2017 this year, and local support has been further optimized. The idea of ​​async functions is to use generators for asynchronous programming, giving them their own semantics and syntax. Therefore, you don't need to use a library to get the encapsulated utility functions, as these are handled behind the scenes.

To run the async/await example in the article, you need a compatible browser.

Running Compatibility

On the client side, Chrome, Firefox and Opera support asynchronous functions well.

Simplify asynchronous code with Async functions (JavaScript development tips)

Starting from version 7.6, Node.js async/await is enabled by default.

Comparison between asynchronous functions and generators

Here is an example of using a generator for asynchronous programming, using the Q library:

var doAsyncOp = Q.async(function* () { 
 
  var val = yield asynchronousOperation(); 
 
  console.log(val); 
 
  return val; 
 
});
Copy after login

Q .async is a wrapper function that handles things after the scene. Where * represents the function as a generator function, yield represents the stop function and is replaced by a wrapper function. Q.async will return a function that you can assign a value to, just like doAsyncOp, and then call it.

The new syntax in ES7 is more concise, the operation example is as follows:

async function doAsyncOp () { 
 
  var val = await asynchronousOperation();      
 
  console.log(val); 
 
  return val; 
 
};
Copy after login

The difference is not big, wedeletean encapsulated function and* symbol, use the async keyword instead. The yield keyword has also been replaced by await. These two examples actually do the same thing: after the asynchronousOperation is completed, assign the value to val, then output and return the result.

Convert Promises into asynchronous functions

What would the previous example look like if we used Vanilla Promises?

function doAsyncOp () { 
 
  return asynchronousOperation().then(function(val) { 
 
    console.log(val); 
 
    return val; 
 
  }); 
 
};
Copy after login

The same here lines of code, but this is because then and the callback function passed to it add a lot of extra code. Another annoyance are the two return keywords. This has always been something that bothered me, because it's hard to figure out what exactly a function using promises returns.

As you can see, this function returns a promise, which will be assigned to val. Guess what the generator and async function examples do! No matter what you return in this function, you are secretly Return a promise that resolves to that value. If you return no value at all, the promise you return implicitly resolves to undefined.

Chained operations

One of the reasons why Promise is so popular is that it can connect multiple asynchronous operations in a chained call, avoiding embedded callbacks. . But async functions are even better than Promise in this regard.

The following demonstrates how to use Promise to perform chain operations (we simply run asynchronousOperation multiple times to demonstrate).

function doAsyncOp() { 
 
  return asynchronousOperation() 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }); 
 
}
Copy after login

To use the async function, you only need to call asynchronousOperation like writing synchronous code:

async function doAsyncOp () { 
 
  var val = await asynchronousOperation(); 
 
  val = await asynchronousOperation(val); 
 
  val = await asynchronousOperation(val); 
 
  return await asynchronousOperation(val); 
 
};
Copy after login

You don’t even need to use it in the final return statement Await, whether used or not, returns a Promise that contains a handleable final value.

Concurrent operations

Promise has another great feature, they can perform multiple asynchronous operations at the same time, and wait for them all to complete before continuing Others event. The ES2015 specification provides Promise.all(), which is used to do this.

Here is an example:

function doAsyncOp() { 
 
  return Promise.all([ 
 
    asynchronousOperation(), 
 
    asynchronousOperation() 
 
  ]).then(function(vals) { 
 
    vals.forEach(console.log); 
 
    return vals; 
 
  }); 
 
}
Copy after login

Promise.all() can also be used as an async function:

async function doAsyncOp() { 
 
  var vals = await Promise.all([ 
 
    asynchronousOperation(), 
 
    asynchronousOperation() 
 
  ]); 
 
  vals.forEach(console.log.bind(console)); 
 
  return vals; 
 
}
Copy after login

Even if Promise.all is used here, the code is still clear.

Handling rejection

Promises 可以被接受(resovled)也可以被拒绝(rejected)。被拒绝的 Promise 可以通过一个函数来处理,这个处理函数要传递给 then,作为其第二个参数,或者传递给 catch 方法。现在我们没有使用 Promise API 中的方法,应该怎么处理拒绝?可以通过 try 和 catch 来处理。使用 async 函数的时候,拒绝被当作错误来传递,这样它们就可以通过 JavaScript 本身支持的错误处理代码来处理。

function doAsyncOp() { 
 
  return asynchronousOperation() 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .catch(function(err) { 
 
      console.error(err); 
 
    }); 
 
}
Copy after login

 

这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。

async function doAsyncOp () { 
 
  try { 
 
    var val = await asynchronousOperation(); 
 
    val = await asynchronousOperation(val); 
 
    return await asynchronousOperation(val); 
 
  } catch (err) { 
 
    console.err(err); 
 
  } 
 
};
Copy after login

 

它不像其它往 async 函数的转换那样简洁,但是确实跟写同步代码一样。如果你在这里不捕捉错误,它会延着调用链一直向上抛出,直到在某处被捕捉处理。如果它一直未被捕捉,它最终会中止程序并抛出一个运行时错误。Promise 以同样的方式运作,只是拒绝不必当作错误来处理;它们可能只是一个说明错误情况的字符串。如果你不捕捉被创建为错误的拒绝,你会看到一个运行时错误,不过如果你只是使用一个字符串,会失败却不会有输出。

中断 Promise

拒绝原生的 Promise,只需要使用 Promise 构建函数中的 reject 就好,当然也可以直接抛出错误——在 Promise 的构造函数中,在 then 或 catch 的回调中抛出都可以。如果是在其它地方抛出错误,Promise 就管不了了。

这里有一些拒绝 Promise 的示例:

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"); 
 
  }); 
 
}
Copy after login

 

一般来说,最好使用 new Error,因为它会包含错误相关的其它信息,比如抛出位置的行号,以及可能会有用的调用栈。

这里有一些抛出 Promise 不能捕捉的错误的示例:

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; 
 
}
Copy after login

 

在 async 函数的 Promise 中抛出错误就不会产生有关范围的问题——你可以在 async 函数中随时随地抛出错误,它总会被 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; 
 
}
Copy after login

 

当然,我们永远不会运行到 doAsyncOp 中的第二个错误,也不会运行到 return 语句,因为在那之前抛出的错误已经中止了函数运行。

问题

如果你刚开始使用 async 函数,需要小心嵌套函数的问题。比如,如果你的 async 函数中有另一个函数(通常是回调),你可能认为可以在其中使用 await ,但实际不能。你只能直接在 async 函数中使用 await 。

比如,这段代码无法运行:

async function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(function(fileName) { 
 
      var file = await getFileAsync(fileName); 
 
      return parse(file); 
 
    }) 
 
  ); 
 
}
Copy after login

 

第 4 行的 await 无效,因为它是在一个普通函数中使用的。不过可以通过为回调函数添加 async 关键字来解决这个问题。

async function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(async function(fileName) { 
 
      var file = await getFileAsync(fileName); 
 
      return parse(file); 
 
    }) 
 
  ); 
 
}
Copy after login

 

你看到它的时候会觉得理所当然,即便如此,仍然需要小心这种情况。

也许你还想知道等价的使用 Promise 的代码:

function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(function(fileName) { 
 
      return getFileAsync(fileName).then(function(file) { 
 
        return parse(file); 
 
      }); 
 
    }) 
 
  ); 
 
}
Copy after login

 

接下来的问题是关于把 async 函数看作同步函数。需要记住的是,async 函数内部的的代码是同步运行的,但是它会立即返回一个 Promise,并继续运行外面的代码,比如:

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
Copy after login

 

你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。

如今,更好的异步代码!

即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。

有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。

The above is the detailed content of Simplify asynchronous code with Async functions (JavaScript development tips). For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template