Maison > Article > interface Web > Parlons à nouveau de la programmation asynchrone JavaScript_compétences Javascript
Avec le développement du front-end, le mot asynchrone devient de plus en plus courant. Supposons que nous ayons maintenant une telle tâche asynchrone :
Initiez plusieurs requêtes au serveur, et les résultats de chaque requête sont utilisés comme paramètres pour la requête suivante.
Jetons un coup d'œil à ce que nous devons faire :
Rappels
La première chose qui nous vient à l’esprit et la plus couramment utilisée est la fonction de rappel. Faisons une simple encapsulation :
let makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall('http://url1', (result) => { result = JSON.parse(result) })
Hmm, ça a l'air plutôt bien ! Mais lorsque l'on essaie d'imbriquer plusieurs tâches, le code ressemble à ceci :
makeAjaxCall('http://url1', (result) => { result = JSON.parse(result) makeAjaxCall(`http://url2?q=${result.query}`, (result) => { result = JSON.parse(result) makeAjaxCall(`http://url3?q=${result.query}`, (result) => { // ... }) }) })
Oh mon Dieu ! Laissez ce tas }) aller au diable !
Nous voulons donc essayer d'utiliser le Modèle d'événement JavaScript :
1. Pub/Sub
Dans le traitement des événements DOM, Pub/Sub est un mécanisme très courant. Par exemple, nous devons ajouter une surveillance des événements aux éléments :
.elem.addEventListener(type, (evt) => { // handler })
Alors pouvons-nous construire un modèle similaire pour gérer les tâches asynchrones ?
La première chose est de construire un centre de distribution et d'ajouter la méthode on/emit :
let PubSub = { events: {}, on(type, handler) { let events = this.events events[type] = events[type] || [] events[type].push(handler) }, emit(type, ...datas) { let events = this.events if (!events[type]) { return } events[type].forEach((handler) => handler(...datas)) } }
Ensuite, nous pouvons l'utiliser comme ceci :
const urls = [ 'http://url1', 'http://url2', 'http://url3' ] let makeAjaxCall = (url) => { // do some ajax PubSub.emit('ajaxEnd', result) } let subscribe = (urls) => { let index = 0 PubSub.on('ajaxEnd', (result) => { result = JSON.parse(result) if (urls[++index]) { makeAjaxCall(`${urls[index]}?q=${result.query}`) } }) makeAjaxCall(urls[0]) }
Il ne semble pas y avoir de changement révolutionnaire par rapport à la fonction de rappel, mais l'avantage de celle-ci est que l'on peut mettre les fonctions de requête et de traitement dans des modules différents pour réduire le couplage.
2. Promesse
Le véritable changement révolutionnaire est la spécification Promise. Avec Promise, nous pouvons effectuer des tâches asynchrones comme celle-ci :
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } makeAjaxCall('http://url1') .then(JSON.parse) .then((result) => makeAjaxCall(`http://url2?q=${result.query}`)) .then(JSON.parse) .then((result) => makeAjaxCall(`http://url3?q=${result.query}`))
Super ! C'est écrit comme une fonction synchrone !
Ne vous inquiétez pas, jeune homme. Nous avons encore mieux :
3. Générateurs
Un autre grand tueur d'ES6 est les générateurs[2]. Dans une fonction génératrice, nous pouvons interrompre l'exécution de la fonction via l'instruction rendement et parcourir les instructions via la méthode suivante en dehors de la fonction. Plus important encore, nous pouvons injecter des données dans la fonction via la méthode suivante pour modifier dynamiquement le comportement de la fonction. fonction. Par exemple :
function* gen() { let a = yield 1 let b = yield a * 2 return b } let it = gen() it.next() // output: {value: 1, done: false} it.next(10) // a = 10, output: {value: 20, done: false} it.next(100) // b = 100, output: {value: 100, done: true}
Encapsulez notre précédente fonction makeAjaxCall via un générateur :
let makeAjaxCall = (url) => { // do some ajax iterator.next(result) } function* requests() { let result = yield makeAjaxCall('http://url1') result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } let iterator = requests() iterator.next() // get everything start
Oh ! La logique semble très claire, mais c'est tellement inconfortable de devoir injecter l'itérateur de l'extérieur à chaque fois...
Ne vous inquiétez pas, mélangeons Promise et Generator et voyons quelle magie noire sera produite :
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } let runGen = (gen) => { let it = gen() let continuer = (value, err) => { let ret try { ret = err ? it.throw(err) : it.next(value) } catch (e) { return Promise.reject(e) } if (ret.done) { return ret.value } return Promise .resolve(ret.value) .then(continuer) .catch((e) => continuer(null, e)) } return continuer() } function* requests() { let result = yield makeAjaxCall('http://url1') result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } runGen(requests)
La fonction runGen ressemble à un automate, tellement génial !
En fait, cette méthode runGen est une implémentation de la fonction asynchrone ECMAScript 7 :
4. Fonction asynchrone
Dans ES7, une fonction asynchrone plus naturelle[3] est introduite. En utilisant la fonction asynchrone, nous pouvons terminer la tâche comme ceci :
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } ;(async () => { let result = await makeAjaxCall('http://url1') result = JSON.parse(result) result = await makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = await makeAjaxCall(`http://url3?q=${result.query}`) })()
Tout comme lorsque nous avons combiné Promise et Generator ci-dessus, le mot-clé wait accepte également une promesse. Dans la fonction asynchrone, les instructions restantes ne seront exécutées qu'une fois l'instruction terminée après wait. L'ensemble du processus est exactement comme si nous utilisions la fonction runGen pour encapsuler le générateur.
Ci-dessus sont plusieurs modes de programmation asynchrone JavaScript résumés dans cet article. J'espère que cela sera utile à l'apprentissage de chacun.