Une brève analyse de Promise dans es6 (avec des exemples)
Le contenu de cet article concerne une brève analyse de Promise dans es6 (avec des exemples). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
Pour l'utilisation de base de Promise, vous pouvez lire "Introduction à ECMAScript 6" par le professeur Ruan Yifeng.
Parlons d’autre chose.
Rappels
Quand on parle de Promise, on commence généralement par les rappels ou l'enfer des rappels. Alors, quels sont les inconvénients de l'utilisation des rappels ?
1. Imbrication de rappels
En utilisant les rappels, nous sommes susceptibles d'écrire du code commercial sous la forme suivante :
doA( function(){ doB(); doC( function(){ doD(); } ) doE(); } ); doF();
Bien sûr, c'est une forme simplifiée. Après quelques réflexions simples, nous pouvons juger que l'ordre d'exécution est :
doA() doF() doB() doC() doE() doD()
Cependant, dans les projets réels, le code sera plus compliqué. Afin de résoudre le problème, nous le faisons. Il est nécessaire d'éviter de nombreux contenus inesthétiques et de passer constamment d'une fonction à l'autre, ce qui rend le dépannage exponentiellement plus difficile.
Bien sûr, la raison de ce problème est que cette méthode d'écriture imbriquée est contraire à la façon de penser linéaire des gens, de sorte que nous devons consacrer plus d'énergie à réfléchir à la séquence d'exécution réelle, l'imbrication et l'indentation ne sont que mineures. des détails qui détournent l’attention de ce processus de réflexion.
Bien sûr, aller à l'encontre de la façon de penser linéaire n'est pas le pire. En fait, nous ajouterons également divers jugements logiques au code, comme dans l'exemple ci-dessus, doD() doit être complété après. doC() est terminé. Que se passe-t-il si l'exécution de doC() échoue ? Voulons-nous réessayer doC() ? Ou accéder directement à d’autres fonctions de gestion des erreurs ? Lorsque nous ajoutons ces jugements au processus, le code devient rapidement trop complexe à maintenir et à mettre à jour.
2. Inversion de contrôle
Lors de l'écriture du code normalement, nous pouvons bien sûr contrôler notre propre code, mais lorsque nous utilisons des rappels, ce rappel peut-il fonctionner ensuite l'exécution cela dépend en fait de l'API qui utilise le callback, par exemple :
// 回调函数是否被执行取决于 buy 模块 import {buy} from './buy.js'; buy(itemData, function(res) { console.log(res) });
Pour l'API fetch que nous utilisons souvent, il n'y a généralement pas de problème, mais si nous utilisons un tiers, qu'en est-il de l'API ?
Lorsque vous appelez une API tierce, l'autre partie exécutera-t-elle la fonction de rappel que vous avez transmise plusieurs fois en raison d'une erreur ?
Afin d'éviter de tels problèmes, vous pouvez ajouter du jugement à votre fonction de rappel. Mais que se passe-t-il si la fonction de rappel n'est pas exécutée en raison d'une erreur ?
Et si cette fonction de rappel était parfois exécutée de manière synchrone et parfois de manière asynchrone ?
Résumons ces situations :
La fonction de rappel est exécutée plusieurs fois
La fonction de rappel n'est pas exécutée
La fonction de rappel est parfois exécutée de manière synchrone et parfois de manière asynchrone
Pour ces situations, vous devrez peut-être effectuer un traitement dans la fonction de rappel et exécuter la fonction de rappel à chaque fois Nous devons effectuer un traitement à chaque fois, ce qui entraîne beaucoup de code répété.
Callback Hell
Regardons d'abord un exemple simple d'enfer de rappel.
Maintenant, pour trouver le fichier le plus volumineux dans un répertoire, les étapes de traitement doivent être :
-
Utilisez
;fs.readdir
pour obtenir la liste des fichiers dans le répertoire Parcourez les fichiers et utilisez
fs.stat
pour obtenir des informations sur les fichiersComparez pour trouver le fichier le plus volumineux
-
avec le plus grand Le rappel est appelé en paramètre avec le nom du fichier.
Le code est :
var fs = require('fs'); var path = require('path'); function findLargest(dir, cb) { // 读取目录下的所有文件 fs.readdir(dir, function(er, files) { if (er) return cb(er); var counter = files.length; var errored = false; var stats = []; files.forEach(function(file, index) { // 读取文件信息 fs.stat(path.join(dir, file), function(er, stat) { if (errored) return; if (er) { errored = true; return cb(er); } stats[index] = stat; // 事先算好有多少个文件,读完 1 个文件信息,计数减 1,当为 0 时,说明读取完毕,此时执行最终的比较操作 if (--counter == 0) { var largest = stats .filter(function(stat) { return stat.isFile() }) .reduce(function(prev, next) { if (prev.size > next.size) return prev return next }) cb(null, files[stats.indexOf(largest)]) } }) }) }) }
La méthode d'utilisation est :
// 查找当前目录最大的文件 findLargest('./', function(er, filename) { if (er) return console.error(er) console.log('largest file was:', filename) });
Vous pouvez copier le code ci-dessus dans un fichier tel que index.js
, puis exécutez node index.js
pour imprimer le nom du fichier le plus volumineux.
Après avoir lu cet exemple, parlons d'autres problèmes de l'enfer des rappels :
1 Difficulté à réutiliser
Une fois l'ordre des rappels déterminé. , il est également très difficile de réutiliser certains liens, car un seul mouvement affecte tout le corps.
Par exemple, si vous souhaitez réutiliser le code pour lire les informations du fichier fs.stat
, car les variables externes sont référencées dans le rappel, le code externe doit être modifié après l'extraction.
2. Les informations de la pile sont déconnectées
Nous savons que le moteur JavaScript maintient une pile de contexte d'exécution Lorsqu'une fonction est exécutée, le contexte d'exécution de la fonction le sera. être créé. Poussé sur la pile, lorsque la fonction termine son exécution, le contexte d'exécution sera retiré de la pile.
Si la fonction B est appelée dans la fonction A, JavaScript poussera d'abord le contexte d'exécution de la fonction A sur la pile, puis poussera le contexte d'exécution de la fonction B sur la pile. Lorsque la fonction B termine son exécution, la fonction B. will Le contexte d'exécution est extrait de la pile. Lorsque la fonction A est exécutée, le contexte d'exécution de la fonction A est extrait de la pile.
L'avantage de ceci est que si nous interrompons l'exécution du code, nous pouvons récupérer les informations complètes de la pile et en obtenir toutes les informations que nous souhaitons.
可是异步回调函数并非如此,比如执行 fs.readdir
的时候,其实是将回调函数加入任务队列中,代码继续执行,直至主线程完成后,才会从任务队列中选择已经完成的任务,并将其加入栈中,此时栈中只有这一个执行上下文,如果回调报错,也无法获取调用该异步操作时的栈中的信息,不容易判定哪里出现了错误。
此外,因为是异步的缘故,使用 try catch 语句也无法直接捕获错误。
(不过 Promise 并没有解决这个问题)
3.借助外层变量
当多个异步计算同时进行,比如这里遍历读取文件信息,由于无法预期完成顺序,必须借助外层作用域的变量,比如这里的 count、errored、stats 等,不仅写起来麻烦,而且如果你忽略了文件读取错误时的情况,不记录错误状态,就会接着读取其他文件,造成无谓的浪费。此外外层的变量,也可能被其它同一作用域的函数访问并且修改,容易造成误操作。
之所以单独讲讲回调地狱,其实是想说嵌套和缩进只是回调地狱的一个梗而已,它导致的问题远非嵌套导致的可读性降低而已。
Promise
Promise 使得以上绝大部分的问题都得到了解决。
1. 嵌套问题
举个例子:
request(url, function(err, res, body) { if (err) handleError(err); fs.writeFile('1.txt', body, function(err) { request(url2, function(err, res, body) { if (err) handleError(err) }) }) });
使用 Promise 后:
request(url) .then(function(result) { return writeFileAsynv('1.txt', result) }) .then(function(result) { return request(url2) }) .catch(function(e){ handleError(e) });
而对于读取最大文件的那个例子,我们使用 promise 可以简化为:
var fs = require('fs'); var path = require('path'); var readDir = function(dir) { return new Promise(function(resolve, reject) { fs.readdir(dir, function(err, files) { if (err) reject(err); resolve(files) }) }) } var stat = function(path) { return new Promise(function(resolve, reject) { fs.stat(path, function(err, stat) { if (err) reject(err) resolve(stat) }) }) } function findLargest(dir) { return readDir(dir) .then(function(files) { let promises = files.map(file => stat(path.join(dir, file))) return Promise.all(promises).then(function(stats) { return { stats, files } }) }) .then(data => { let largest = data.stats .filter(function(stat) { return stat.isFile() }) .reduce((prev, next) => { if (prev.size > next.size) return prev return next }) return data.files[data.stats.indexOf(largest)] }) }
2. 控制反转再反转
前面我们讲到使用第三方回调 API 的时候,可能会遇到如下问题:
回调函数执行多次
回调函数没有执行
回调函数有时同步执行有时异步执行
对于第一个问题,Promise 只能 resolve 一次,剩下的调用都会被忽略。
对于第二个问题,我们可以使用 Promise.race 函数来解决:
function timeoutPromise(delay) { return new Promise( function(resolve,reject){ setTimeout( function(){ reject( "Timeout!" ); }, delay ); } ); } Promise.race( [ foo(), timeoutPromise( 3000 ) ] ) .then(function(){}, function(err){});
对于第三个问题,为什么有的时候会同步执行有的时候回异步执行呢?
我们来看个例子:
var cache = {...}; function downloadFile(url) { if(cache.has(url)) { // 如果存在cache,这里为同步调用 return Promise.resolve(cache.get(url)); } return fetch(url).then(file => cache.set(url, file)); // 这里为异步调用 } console.log('1'); getValue.then(() => console.log('2')); console.log('3');
在这个例子中,有 cahce 的情况下,打印结果为 1 2 3,在没有 cache 的时候,打印结果为 1 3 2。
然而如果将这种同步和异步混用的代码作为内部实现,只暴露接口给外部调用,调用方由于无法判断是到底是异步还是同步状态,影响程序的可维护性和可测试性。
简单来说就是同步和异步共存的情况无法保证程序逻辑的一致性。
然而 Promise 解决了这个问题,我们来看个例子:
var promise = new Promise(function (resolve){ resolve(); console.log(1); }); promise.then(function(){ console.log(2); }); console.log(3); // 1 3 2
即使 promise 对象立刻进入 resolved 状态,即同步调用 resolve 函数,then 函数中指定的方法依然是异步进行的。
PromiseA+ 规范也有明确的规定:
实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
Promise 反模式
1.Promise 嵌套
// bad loadSomething().then(function(something) { loadAnotherthing().then(function(another) { DoSomethingOnThem(something, another); }); });
// good Promise.all([loadSomething(), loadAnotherthing()]) .then(function ([something, another]) { DoSomethingOnThem(...[something, another]); });
2.断开的 Promise 链
// bad function anAsyncCall() { var promise = doSomethingAsync(); promise.then(function() { somethingComplicated(); }); return promise; }
// good function anAsyncCall() { var promise = doSomethingAsync(); return promise.then(function() { somethingComplicated() }); }
3.混乱的集合
// bad function workMyCollection(arr) { var resultArr = []; function _recursive(idx) { if (idx >= resultArr.length) return resultArr; return doSomethingAsync(arr[idx]).then(function(res) { resultArr.push(res); return _recursive(idx + 1); }); } return _recursive(0); }
你可以写成:
function workMyCollection(arr) { return Promise.all(arr.map(function(item) { return doSomethingAsync(item); })); }
如果你非要以队列的形式执行,你可以写成:
function workMyCollection(arr) { return arr.reduce(function(promise, item) { return promise.then(function(result) { return doSomethingAsyncWithResult(item, result); }); }, Promise.resolve()); }
4.catch
// bad somethingAync.then(function() { return somethingElseAsync(); }, function(err) { handleMyError(err); });
如果 somethingElseAsync 抛出错误,是无法被捕获的。你可以写成:
// good somethingAsync .then(function() { return somethingElseAsync() }) .then(null, function(err) { handleMyError(err); });
// good somethingAsync() .then(function() { return somethingElseAsync(); }) .catch(function(err) { handleMyError(err); });
红绿灯问题
题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用 Promse 实现)
三个亮灯函数已经存在:
function red(){ console.log('red'); } function green(){ console.log('green'); } function yellow(){ console.log('yellow'); }
利用 then 和递归实现:
function red(){ console.log('red'); } function green(){ console.log('green'); } function yellow(){ console.log('yellow'); } var light = function(timmer, cb){ return new Promise(function(resolve, reject) { setTimeout(function() { cb(); resolve(); }, timmer); }); }; var step = function() { Promise.resolve().then(function(){ return light(3000, red); }).then(function(){ return light(2000, green); }).then(function(){ return light(1000, yellow); }).then(function(){ step(); }); } step();
promisify
有的时候,我们需要将 callback 语法的 API 改造成 Promise 语法,为此我们需要一个 promisify 的方法。
因为 callback 语法传参比较明确,最后一个参数传入回调函数,回调函数的第一个参数是一个错误信息,如果没有错误,就是 null,所以我们可以直接写出一个简单的 promisify 方法:
function promisify(original) { return function (...args) { return new Promise((resolve, reject) => { args.push(function callback(err, ...values) { if (err) { return reject(err); } return resolve(...values) }); original.call(this, ...args); }); }; }
Promise 的局限性
1. 错误被吃掉
首先我们要理解,什么是错误被吃掉,是指错误信息不被打印吗?
并不是,举个例子:
throw new Error('error'); console.log(233333);
在这种情况下,因为 throw error 的缘故,代码被阻断执行,并不会打印 233333,再举个例子:
const promise = new Promise(null); console.log(233333);
以上代码依然会被阻断执行,这是因为如果通过无效的方式使用 Promise,并且出现了一个错误阻碍了正常 Promise 的构造,结果会得到一个立刻跑出的异常,而不是一个被拒绝的 Promise。
然而再举个例子:
let promise = new Promise(() => { throw new Error('error') }); console.log(2333333);
这次会正常的打印 233333
,说明 Promise 内部的错误不会影响到 Promise 外部的代码,而这种情况我们就通常称为 “吃掉错误”。
其实这并不是 Promise 独有的局限性,try..catch 也是这样,同样会捕获一个异常并简单的吃掉错误。
而正是因为错误被吃掉,Promise 链中的错误很容易被忽略掉,这也是为什么会一般推荐在 Promise 链的最后添加一个 catch 函数,因为对于一个没有错误处理函数的 Promise 链,任何错误都会在链中被传播下去,直到你注册了错误处理函数。
2. 单一值
Promise 只能有一个完成值或一个拒绝原因,然而在真实使用的时候,往往需要传递多个值,一般做法都是构造一个对象或数组,然后再传递,then 中获得这个值后,又会进行取值赋值的操作,每次封装和解封都无疑让代码变得笨重。
说真的,并没有什么好的方法,建议是使用 ES6 的解构赋值:
Promise.all([Promise.resolve(1), Promise.resolve(2)]) .then(([x, y]) => { console.log(x, y); });
3. 无法取消
Promise 一旦新建它就会立即执行,无法中途取消。
4. 无法得知 pending 状态
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
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!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds



PHP et Vue : une combinaison parfaite d'outils de développement front-end À l'ère actuelle de développement rapide d'Internet, le développement front-end est devenu de plus en plus important. Alors que les utilisateurs ont des exigences de plus en plus élevées en matière d’expérience des sites Web et des applications, les développeurs front-end doivent utiliser des outils plus efficaces et plus flexibles pour créer des interfaces réactives et interactives. En tant que deux technologies importantes dans le domaine du développement front-end, PHP et Vue.js peuvent être considérés comme une arme parfaite lorsqu'ils sont associés. Cet article explorera la combinaison de PHP et Vue, ainsi que des exemples de code détaillés pour aider les lecteurs à mieux comprendre et appliquer ces deux éléments.

Dans la vie quotidienne, nous rencontrons souvent des problèmes entre promesses et réalisation. Que ce soit dans une relation personnelle ou dans une transaction commerciale, tenir ses promesses est essentiel pour instaurer la confiance. Cependant, les avantages et les inconvénients de l’engagement sont souvent controversés. Cet article explorera les avantages et les inconvénients des engagements et donnera quelques conseils sur la façon de tenir parole. Les avantages promis sont évidents. Premièrement, l’engagement renforce la confiance. Lorsqu’une personne tient parole, elle fait croire aux autres qu’elle est une personne digne de confiance. La confiance est le lien établi entre les personnes, qui peut les rendre plus

Lors des entretiens de développement front-end, les questions courantes couvrent un large éventail de sujets, notamment les bases HTML/CSS, les bases JavaScript, les frameworks et les bibliothèques, l'expérience du projet, les algorithmes et les structures de données, l'optimisation des performances, les requêtes inter-domaines, l'ingénierie front-end, les modèles de conception et les nouvelles technologies et tendances. Les questions de l'intervieweur sont conçues pour évaluer les compétences techniques du candidat, son expérience en matière de projet et sa compréhension des tendances du secteur. Par conséquent, les candidats doivent être parfaitement préparés dans ces domaines pour démontrer leurs capacités et leur expertise.

Tutoriel JavaScript : Comment obtenir le code d'état HTTP, des exemples de code spécifiques sont requis Préface : Dans le développement Web, l'interaction des données avec le serveur est souvent impliquée. Lors de la communication avec le serveur, nous devons souvent obtenir le code d'état HTTP renvoyé pour déterminer si l'opération a réussi et effectuer le traitement correspondant en fonction de différents codes d'état. Cet article vous apprendra comment utiliser JavaScript pour obtenir des codes d'état HTTP et fournira quelques exemples de codes pratiques. Utilisation de XMLHttpRequest

Django est un framework d'application Web écrit en Python qui met l'accent sur un développement rapide et des méthodes propres. Bien que Django soit un framework Web, pour répondre à la question de savoir si Django est un front-end ou un back-end, vous devez avoir une compréhension approfondie des concepts de front-end et de back-end. Le front-end fait référence à l'interface avec laquelle les utilisateurs interagissent directement, et le back-end fait référence aux programmes côté serveur. Ils interagissent avec les données via le protocole HTTP. Lorsque le front-end et le back-end sont séparés, les programmes front-end et back-end peuvent être développés indépendamment pour mettre en œuvre respectivement la logique métier et les effets interactifs, ainsi que l'échange de données.

Une explication détaillée de Promise.resolve() nécessite des exemples de code spécifiques. Promise est un mécanisme en JavaScript pour gérer les opérations asynchrones. Dans le développement réel, il est souvent nécessaire de traiter certaines tâches asynchrones qui doivent être exécutées dans l'ordre, et la méthode Promise.resolve() est utilisée pour renvoyer un objet Promise qui a été rempli. Promise.resolve() est une méthode statique de la classe Promise, qui accepte un

Introduction à la méthode d'obtention du code d'état HTTP en JavaScript : Dans le développement front-end, nous devons souvent gérer l'interaction avec l'interface back-end, et le code d'état HTTP en est une partie très importante. Comprendre et obtenir les codes d'état HTTP nous aide à mieux gérer les données renvoyées par l'interface. Cet article explique comment utiliser JavaScript pour obtenir des codes d'état HTTP et fournit des exemples de code spécifiques. 1. Qu'est-ce que le code d'état HTTP ? Le code d'état HTTP signifie que lorsque le navigateur lance une requête au serveur, le service

En tant que langage de programmation rapide et efficace, le langage Go est très populaire dans le domaine du développement back-end. Cependant, peu de gens associent le langage Go au développement front-end. En fait, l’utilisation du langage Go pour le développement front-end peut non seulement améliorer l’efficacité, mais également ouvrir de nouveaux horizons aux développeurs. Cet article explorera la possibilité d'utiliser le langage Go pour le développement front-end et fournira des exemples de code spécifiques pour aider les lecteurs à mieux comprendre ce domaine. Dans le développement front-end traditionnel, JavaScript, HTML et CSS sont souvent utilisés pour créer des interfaces utilisateur.
