Maison > Article > interface Web > Simplifiez le code asynchrone avec les fonctions Async (conseils de développement JavaScript)
Les promesses sont devenues populaires sur Internet lorsqu'elles ont été publiées pour la première fois sur JavaScript : elles aident les développeurs à échapper à l'enfer des rappels et à résoudre les problèmes d'asynchronie qui affligent les développeurs JavaScript dans de nombreux endroits. Mais les promesses sont loin d’être parfaites. Ils continuent de demander des rappels, peuvent encore être un peu compliqués et incroyablement redondants sur certains problèmes complexes.
Avec l'arrivée d'ES6 (maintenant appelé ES2015), en plus de l'introduction des spécifications Promise et de la nécessité de demander ces innombrables bibliothèques, nous avons également des générateurs. Les générateurs peuvent arrêter l'exécution à l'intérieur d'une fonction , ce qui signifie qu'ils peuvent être enveloppés dans une fonction polyvalente et que nous pouvons attendre la fin de l'opération asynchrone avant de passer à la ligne de code suivante. Soudain, votre code asynchrone peut commencer à paraître synchrone.
Ce n'est que la première étape. Les fonctions asynchrones ont été standardisées depuis leur ajout à ES2017 cette année, et la prise en charge locale a été encore optimisée. L'idée des fonctions asynchrones est d'utiliser des générateurs pour la programmation asynchrone, en leur donnant leur propre sémantique et syntaxe. Par conséquent, vous n’avez pas besoin d’utiliser une bibliothèque pour obtenir les fonctions utilitaires encapsulées, car celles-ci sont gérées en arrière-plan.
Pour exécuter l'exemple async/await dans l'article, vous avez besoin d'un navigateur compatible.
Compatible avec le fonctionnement
Côté client, Chrome, Firefox et Opera ont un bon support pour les fonctions asynchrones.
À partir de la version 7.6, Node.js active async/await par défaut.
Comparaison entre fonctions asynchrones et générateurs
Voici un exemple d'utilisation de générateurs pour la programmation asynchrone, en utilisant la bibliothèque Q :
var doAsyncOp = Q.async(function* () { var val = yield asynchronousOperation(); console.log(val); return val; });
Q .async est une fonction wrapper qui gère les choses après la scène. Où * représente la fonction en tant que fonction génératrice, rendement représente la fonction d'arrêt et est remplacé par une fonction wrapper. Q.async renverra une fonction à laquelle vous pourrez attribuer une valeur, tout comme doAsyncOp, puis l'appeler.
La nouvelle syntaxe dans ES7 est plus concise. L'exemple d'opération est le suivant :
async function doAsyncOp () { var val = await asynchronousOperation(); console.log(val); return val; };
Il n'y a pas beaucoup de différence. > une fonction encapsulée et des symboles *, utilisez plutôt le mot-clé async. Le mot clé rendement a également été remplacé par wait. Ces deux exemples font en fait la même chose : une fois l'opération asynchrone terminée, attribuez la valeur à val, puis affichez et renvoyez le résultat. Convertir les promesses en fonctions asynchrones
À quoi ressemblerait l'exemple précédent si nous utilisions Vanilla Promises ?
function doAsyncOp () { return asynchronousOperation().then(function(val) { console.log(val); return val; }); };
Ici, c'est le même numéro de lignes de code, mais c'est parce qu'alors et la
fonction de rappelqui lui est transmise ajoutent beaucoup de code supplémentaire. Un autre désagrément concerne les deux mots-clés de retour. Cela a toujours été quelque chose qui m'a dérangé, car il est difficile de comprendre ce que renvoie exactement une fonction utilisant des promesses. Comme vous pouvez le voir, cette fonction renvoie une promesse, qui sera affectée à val. Devinez ce que font les exemples de générateur et de fonction asynchrone ! Quoi que vous renvoyiez dans cette fonction, vous renvoyez secrètement une promesse qui se résout à ! cette valeur. Si vous ne renvoyez aucune valeur, la promesse que vous renvoyez est implicitement résolue en indéfini.
Opérations chaînées
L'une des raisons pour lesquelles Promise est si populaire est qu'il peut connecter plusieurs opérations asynchrones dans un appel chaîné, évitant ainsi les rappels intégrés. Mais les fonctions asynchrones sont encore meilleures que Promise à cet égard.
Ce qui suit montre comment utiliser Promise pour effectuer des opérations en chaîne (nous exécutons simplement asynchronousOperation plusieurs fois pour le démontrer).
function doAsyncOp() { return asynchronousOperation() .then(function(val) { return asynchronousOperation(val); }) .then(function(val) { return asynchronousOperation(val); }) .then(function(val) { return asynchronousOperation(val); }); }
En utilisant la fonction async, il vous suffit d'appeler asynchronousOperation comme écrire du code synchrone :
async function doAsyncOp () { var val = await asynchronousOperation(); val = await asynchronousOperation(val); val = await asynchronousOperation(val); return await asynchronousOperation(val); };
Même en finale instruction return Il n'est pas nécessaire d'utiliser wait, car qu'elle soit utilisée ou non, elle renvoie une promesse qui contient une valeur finale gérable.
Opérations simultanées
Promise a une autre fonctionnalité intéressante, ils peuvent effectuer plusieurs opérations asynchrones en même temps et attendre qu'elles soient toutes terminées avant de continuer
AutresÉvénement . La spécification ES2015 fournit Promise.all(), qui est utilisée pour ce faire. Voici un exemple :
function doAsyncOp() { return Promise.all([ asynchronousOperation(), asynchronousOperation() ]).then(function(vals) { vals.forEach(console.log); return vals; }); }
Promise.all() peut également être utilisé comme fonction asynchrone :
async function doAsyncOp() { var vals = await Promise.all([ asynchronousOperation(), asynchronousOperation() ]); vals.forEach(console.log.bind(console)); return vals; }
Même si Promise.all est utilisé ici, le code reste très clair.
Gérer les refus
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); }); }
这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。
async function doAsyncOp () { try { var val = await asynchronousOperation(); val = await asynchronousOperation(val); return await asynchronousOperation(val); } catch (err) { console.err(err); } };
它不像其它往 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"); }); }
一般来说,最好使用 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; }
在 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; }
当然,我们永远不会运行到 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); }) ); }
第 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); }); }) ); }
接下来的问题是关于把 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
你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。
如今,更好的异步代码!
即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。
有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!