有关 Async v1.5.x 文档,请参阅 此处
¥For Async v1.5.x documentation, go HERE
Async 是一个实用工具模块,它提供直接、强大的函数来处理异步 JavaScript。虽然最初设计用于 Node.js 并可通过 npm i async
安装,但它也可以直接在浏览器中使用。
¥Async is a utility module which provides straight-forward, powerful functions
for working with asynchronous JavaScript. Although originally designed for
use with Node.js and installable via npm i async
,
it can also be used directly in the browser.
Async 也可以通过以下方式安装:
¥Async is also installable via:
- yarn:
yarn add async
Async 提供大约 70 个函数,包括常见的 '功能' 方法(map
、reduce
、filter
、each
……)以及一些异步控制流的常见模式(parallel
、series
、waterfall
……)。所有这些函数都假设你遵循 Node.js 约定,即提供一个回调作为异步函数的最后一个参数 - 该回调期望将 Error 作为其第一个参数 - 并调用一次回调。
¥Async provides around 70 functions that include the usual 'functional'
suspects (map
, reduce
, filter
, each
…) as well as some common patterns
for asynchronous control flow (parallel
, series
, waterfall
…). All these
functions assume you follow the Node.js convention of providing a single
callback as the last argument of your asynchronous function -- a callback which expects an Error as its first argument -- and calling the callback once.
你还可以将 async
函数传递给 Async 方法,而不是回调接受函数。有关更多信息,请参阅 AsyncFunction
¥You can also pass async
functions to Async methods, instead of callback-accepting functions. For more information, see AsyncFunction
快速示例
¥Quick Examples
async.map(['file1','file2','file3'], fs.stat, function(err, results) {
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], function(filePath, callback) {
fs.access(filePath, function(err) {
callback(null, !err)
});
}, function(err, results) {
// results now equals an array of the existing files
});
async.parallel([
function(callback) { ... },
function(callback) { ... }
], function(err, results) {
// optional callback
});
async.series([
function(callback) { ... },
function(callback) { ... }
]);
还有更多可用的函数,因此请查看下面的文档以获取完整列表。本模块旨在全面,因此如果你觉得缺少任何内容,请为其创建 GitHub 问题。
¥There are many more functions available so take a look at the docs below for a full list. This module aims to be comprehensive, so if you feel anything is missing please create a GitHub issue for it.
常见陷阱 (StackOverflow)
¥Common Pitfalls (StackOverflow)
同步迭代函数
¥Synchronous iteration functions
如果在使用异步时出现类似 RangeError: Maximum call stack size exceeded.
或其他堆栈溢出问题的错误,则你可能正在使用同步迭代器。同步是指在 javascript 事件循环中,函数在同一时刻调用其回调,而不执行任何 I/O 或使用任何计时器。反复调用许多回调将很快导致堆栈溢出。如果你遇到此问题,只需使用 async.setImmediate
推迟回调以在事件循环的下一个滴答中启动新的调用堆栈。
¥If you get an error like RangeError: Maximum call stack size exceeded.
or other stack overflow issues when using async, you are likely using a synchronous iteratee. By synchronous we mean a function that calls its callback on the same tick in the javascript event loop, without doing any I/O or using any timers. Calling many callbacks iteratively will quickly overflow the stack. If you run into this issue, just defer your callback with async.setImmediate
to start a new call stack on the next tick of the event loop.
如果你在某些情况下提前回调,也可能意外发生这种情况:
¥This can also arise by accident if you callback early in certain cases:
async.eachSeries(hugeArray, function iteratee(item, callback) {
if (inCache(item)) {
callback(null, cache[item]); // if many items are cached, you'll overflow
} else {
doSomeIO(item, callback);
}
}, function done() {
//...
});
只需将其更改为:
¥Just change it to:
async.eachSeries(hugeArray, function iteratee(item, callback) {
if (inCache(item)) {
async.setImmediate(function() {
callback(null, cache[item]);
});
} else {
doSomeIO(item, callback);
//...
}
});
出于性能原因,Async 不会防范同步迭代器。如果你仍然遇到堆栈溢出,你可以按照上面的建议进行延迟,或者使用 async.ensureAsync
封装函数。本质上是异步的函数没有这个问题,也不需要额外的回调延迟。
¥Async does not guard against synchronous iteratees for performance reasons. If you are still running into stack overflows, you can defer as suggested above, or wrap functions with async.ensureAsync
Functions that are asynchronous by their nature do not have this problem and don't need the extra callback deferral.
如果 JavaScript 的事件循环仍然有点模糊,请查看 本文 或 本次演讲 以获取有关其工作原理的更多详细信息。
¥If JavaScript's event loop is still a bit nebulous, check out this article or this talk for more detailed information about how it works.
多个回调
¥Multiple callbacks
确保在尽早调用回调时始终使用 return
,否则在许多情况下会导致多个回调和不可预测的行为。
¥Make sure to always return
when calling a callback early, otherwise you will cause multiple callbacks and unpredictable behavior in many cases.
async.waterfall([
function(callback) {
getSomething(options, function (err, result) {
if (err) {
callback(new Error("failed getting something:" + err.message));
// we should return here
}
// since we did not return, this callback still will be called and
// `processData` will be called twice
callback(null, result);
});
},
processData
], done)
当回调调用不是函数的最后一条语句时,始终使用 return callback(err, result)
是一种很好的做法。
¥It is always good practice to return callback(err, result)
whenever a callback call is not the last statement of a function.
使用 ES2017 async
函数
¥Using ES2017 async
functions
只要我们接受 Node 样式的回调函数,Async 就会接受 async
函数。但是,我们不会向它们传递回调,而是使用返回值并处理任何承诺拒绝或抛出的错误。
¥Async accepts async
functions wherever we accept a Node-style callback function. However, we do not pass them a callback, and instead use the return value and handle any promise rejections or errors thrown.
async.mapLimit(files, 10, async file => { // <- no callback!
const text = await util.promisify(fs.readFile)(dir + file, 'utf8')
const body = JSON.parse(text) // <- a parse error here will be caught automatically
if (!(await checkValidity(body))) {
throw new Error(`${file} has invalid contents`) // <- this error will also be caught
}
return body // <- return a value!
}, (err, contents) => {
if (err) throw err
console.log(contents)
})
我们只能检测原生 async
函数,而不能检测转译版本(例如使用 Babel)。否则,你可以将 async
函数封装在 async.asyncify()
中。
¥We can only detect native async
functions, not transpiled versions (e.g. with Babel). Otherwise, you can wrap async
functions in async.asyncify()
.
将上下文绑定到迭代器
¥Binding a context to an iteratee
本节实际上是关于 bind
,而不是关于 Async。如果你想知道如何让 Async 在给定上下文中执行你的迭代器,或者对为什么另一个库的方法不能作为迭代器工作感到困惑,请研究此示例:
¥This section is really about bind
, not about Async. If you are wondering how to
make Async execute your iteratees in a given context, or are confused as to why
a method of another library isn't working as an iteratee, study this example:
// Here is a simple object with an (unnecessarily roundabout) squaring method
var AsyncSquaringLibrary = {
squareExponent: 2,
square: function(number, callback){
var result = Math.pow(number, this.squareExponent);
setTimeout(function(){
callback(null, result);
}, 200);
}
};
async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result) {
// result is [NaN, NaN, NaN]
// This fails because the `this.squareExponent` expression in the square
// function is not evaluated in the context of AsyncSquaringLibrary, and is
// therefore undefined.
});
async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result) {
// result is [1, 4, 9]
// With the help of bind we can attach a context to the iteratee before
// passing it to Async. Now the square function will be executed in its
// 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`
// will be as expected.
});
细微内存泄漏
¥Subtle Memory Leaks
在某些情况下,当在另一个异步函数内调用 Async 方法时,你可能希望提前退出异步流:
¥There are cases where you might want to exit early from async flow, when calling an Async method inside another async function:
function myFunction (args, outerCallback) {
async.waterfall([
//...
function (arg, next) {
if (someImportantCondition()) {
return outerCallback(null)
}
},
function (arg, next) {/*...*/}
], function done (err) {
//...
})
}
瀑布中发生了一些事情,你想跳过其余的执行,因此你调用了外部回调。但是,Async 仍将等待调用内部 next
回调,从而分配一些闭包范围。
¥Something happened in a waterfall where you want to skip the rest of the execution, so you call an outer callack. However, Async will still wait for that inner next
callback to be called, leaving some closure scope allocated.
从 3.0 版开始,你可以使用 false
作为 error
参数调用任何 Async 回调,其余的 Async 方法的执行将被停止或忽略。
¥As of version 3.0, you can call any Async callback with false
as the error
argument, and the rest of the execution of the Async method will be stopped or ignored.
function (arg, next) {
if (someImportantCondition()) {
outerCallback(null)
return next(false) // ← signal that you called an outer callback
}
},
在处理集合时对其进行变异
¥Mutating collections while processing them
如果你将数组传递给集合方法(例如 each
、mapLimit
或 filterSeries
),然后尝试将其他项目 push
、pop
或 splice
添加到数组上,这可能会导致意外或未定义的行为。Async 将迭代直到满足数组的原始 length
,并且项目 pop()
或 splice()
d 的索引可能已经被处理。因此,不建议在 Async 开始迭代数组后修改数组。如果你确实需要 push
、pop
或 splice
,请改用 queue
。
¥If you pass an array to a collection method (such as each
, mapLimit
, or filterSeries
), and then attempt to push
, pop
, or splice
additional items on to the array, this could lead to unexpected or undefined behavior. Async will iterate until the original length
of the array is met, and the indexes of items pop()
ed or splice()
d could already have been processed. Therefore, it is not recommended to modify the array after Async has begun iterating over it. If you do need to push
, pop
, or splice
, use a queue
instead.
下载
¥Download
源代码可从 GitHub 下载。或者,你可以使用 npm 安装:
¥The source is available for download from GitHub. Alternatively, you can install using npm:
$ npm i async
然后你可以像往常一样 require()
异步:
¥You can then require()
async as normal:
var async = require("async");
或需要单独的方法:
¥Or require individual methods:
var waterfall = require("async/waterfall");
var map = require("async/map");
开发:async.js - 29.6kb 未压缩
¥Development: async.js - 29.6kb Uncompressed
在浏览器中
¥In the Browser
Async 应该可以在任何 ES2015 环境中工作(Node 6+ 和所有现代浏览器)。
¥Async should work in any ES2015 environment (Node 6+ and all modern browsers).
如果你想在较旧的环境中使用 Async(例如 Node 4、IE11),则必须进行转译。
¥If you want to use Async in an older environment, (e.g. Node 4, IE11) you will have to transpile.
用法:
¥Usage:
<script type="text/javascript" src="async.js"></script>
<script type="text/javascript">
async.map(data, asyncProcess, function(err, results) {
alert(results);
});
</script>
Async 的可移植版本(包括 async.js
和 async.min.js
)包含在 /dist
文件夹中。也可以在 jsDelivr CDN 上找到 Async。
¥The portable versions of Async, including async.js
and async.min.js
, are
included in the /dist
folder. Async can also be found on the jsDelivr CDN.
ES 模块
¥ES Modules
Async 包含一个 .mjs
版本,兼容的打包器(如 Webpack 或 Rollup)应自动使用该版本,任何使用 package.json
的 module
字段的程序都应自动使用该版本。
¥Async includes a .mjs
version that should automatically be used by compatible bundlers such as Webpack or Rollup, anything that uses the module
field of the package.json
.
我们还在 npm 上的替代 async-es
包中提供了 Async 作为纯 ES2015 模块的集合。
¥We also provide Async as a collection of purely ES2015 modules, in an alternative async-es
package on npm.
$ npm install async-es
import waterfall from 'async-es/waterfall';
import async from 'async-es';
Typescript
Async 有第三方类型定义。
¥There are third-party type definitions for Async.
npm i -D @types/async
建议在你的 tsconfig.json
中以 ES2017 或更高版本为目标,以便保留 async
函数:
¥It is recommended to target ES2017 or higher in your tsconfig.json
, so async
functions are preserved:
{
"compilerOptions": {
"target": "es2017"
}
}
其他库
¥Other Libraries
-
limiter
是一个基于每秒/小时请求数的速率限制包。¥
limiter
a package for rate-limiting based on requests per sec/hour. -
neo-async
是 Async 的替代实现,专注于速度。¥
neo-async
an altername implementation of Async, focusing on speed. -
co-async
是一个受 Async 启发的库,用于co
和生成器函数。¥
co-async
a library inspired by Async for use withco
and generator functions. -
promise-async
是 Async 的一个版本,其中所有方法都是 Promisified。¥
promise-async
a version of Async where all the methods are Promisified. -
'modern-async' 是仅使用 async/await 和 promise 的 Async 替代方案。
¥'modern-async' an alternative to Async using only async/await and promises.