De la promesse à l'attente

Dans l' article précédent, j'ai fini de parler de la naissance de la promesse à partir d'une opération asynchrone, mais la promesse ne réalise que la séparation de l'opération asynchrone et du résultat. Lorsque nous avons plusieurs opérations asynchrones et qu'il existe des dépendances avant et après les résultats, nous ne pouvons pas éviter , il y aura une série de méthodes .then. Toujours pas très élégant.

La situation idéale est que nous pouvons écrire des opérations asynchrones comme synchrones. Lors de l'exécution d'une opération asynchrone, laissez le code y rester et attendez que l'opération asynchrone se termine avant d'exécuter le code suivant . 【Note 1】

Ici, vous devez d'abord comprendre les itérateurs et les générateurs.

1. Objets itérables et itérateurs

Iterator est un itérateur proposé par ES6. Étant donné que ES6 a ajouté Set et Map à la structure de données, ainsi que le tableau et l'objet existants, les utilisateurs peuvent également les combiner pour former une structure de données combinée.Les structures de données complexes rendent la traversée de la boucle plus difficile.Afin de simplifier et d'unifier la boucle mode, ES6 fournit l'interface de l'itérateur (Iterator) pour fournir un mécanisme d'accès unifié for..ofou ... expandeur.

L'itération signifie l'enregistrement des résultats tout en parcourant .

Il fournit un mécanisme d'accès unifié pour une variété de structures de données différentes . Fournit une méthode d'accès séquentiel à chaque élément de la collection, nous permettant de parcourir facilement tous les éléments de la collection.

1.1, la différence entre les objets itérables et les itérateurs

En fait, es6 a ajouté le concept d'itérateurs.Tous les objets qui répondent au protocole d'itérateur sont des objets itérables . Ici, nous devons distinguer les concepts d'objets itérables et d'itérateurs.

objet itérable itérateur
Méthodes disponibles for...ofet...展开符 méthode suivante()
source array, str, set, map, etc. ont la méthode [Symbol.iterator], et cette méthode renvoie un objet itérateur Il a la méthode suivante, qui peut déplacer le pointeur pour réaliser l'objet de traverser l'objet itérable
exemple array,str,set,map等 Le retour de la méthode [Symbol.iterator] de array, str, set, map, etc.

Protocole itérateur :

具备[Symbol.iterator]方法,且该方法会返回一个迭代器对象,该对象的特征是具备next方法,能够进行迭代。

1.2, le principe de fonctionnement de l'itérateur

创建一个指针对象,指向当前数据结构的起始位置
第一次调用next方法时,指针指向数据结构的第一个成员
接下来调用next方法,指针后移,直到指向最后一个成员

Continuez à appeler nextla procédure de récupération répétée et renvoyez un résultat à chaque fois. Se termine lorsqu'il n'y a rien à retourner. Par conséquent, nextl'objet renvoyé a deux propriétés doneet value. doneIndique si elle est terminée valueet indique le résultat de l'itération en cours. Lorsqu'il doneest true, cela signifie que l'itération est terminée. À ce stade, aucun résultat n'est renvoyé, c'est-à-dire qu'il n'y a pas d' valueattribut de ce type.

//迭代器生成函数
function myIterator(arr) {
    
    
    let index = 0
    return {
    
    
        next: function() {
    
    
            let done = ( index >= arr.length );
            let value = ! done ? arr[index++] : undefined;
            
            return {
    
     value, done };
        }
    }
}

let myIter = myIterator([1,2,3]);
console.log(myIter.next());//{ value: 1, done: false }
console.log(myIter.next());//{ value: 2, done: false } 
console.log(myIter.next());//{ value: 3, done: false }  
console.log(myIter.next());//{ value: undefined, done: true }

1.3, l'itérateur par défaut de Array et d'autres structures de données

Les objets itérables natifs dans ES6 sont Array, Set, Map et String. for..ofIls peuvent être traversés car ils ont Symbol.iteratordes propriétés sur leurs objets prototypes, qui pointent vers la méthode d'itération par défaut de la structure de données. ** Lors de l'utilisation d' for...of..objets itérables itératifs, le js moteur appellera sa Symbol.iteratorméthode pour renvoyer l'itérateur par défaut correspondant. Exécutez ensuite la méthode suivante. **Exemple:

var arr = [1, 2, 3, 4, 5];     //数组是一个迭代器
// 使用for..of时,,js引擎就会调用其`Symbol.iterator`方法,从而返回相应数据的默认迭代器
for(var v of arr){
    
    
    console.log(v); // 12345
}

Ainsi, puisqu'il a une méthode sur son objet prototype Symbol.iteratoret renvoie l'itérateur par défaut correspondant, nous pouvons l'utiliser pour générer l'itérateur correspondant, puis utiliser la méthode next() pour accéder à la valeur :

var arr = [1, 2, 3, 4, 5];     //数组是一个迭代器
//arr[Symbol.iterator]()会返回arr的默认迭代器,于是就可以使用next
var it = arr[Symbol.iterator]();
console.log(it.next())//{ value: 1, done: false }
console.log(it.next())//{ value: 2, done: false }
console.log(it.next())//{ value: 3, done: false }
console.log(...it)//4 5,只打印出剩余的没有被迭代的,所以...应该也是利用的迭代器的next
console.log(it.next())//{ value: undefined, done: true }//剩余的两个值被...迭代过了,于是这里就结束了

1.2, écrire un objet itérable à la main

En d'autres termes, tant qu'une structure de données a Symbol.iteratorune méthode et Symbol.iteratorque la méthode renvoie une méthode suivante, elle peut être considérée comme un objet itérable (itérable) .

Autrement dit, deux conditions doivent être remplies :

1,该对象具备Symbol.iterator方法
2,Symbol.iterator方法返回一个对象,该对象具备next方法(迭代器)。
// 实现一个可迭代对象
const myIterable = {
    
    
  data: [1, 2, 3],
  [Symbol.iterator]() {
    
    
    let index = 0;
    const data = this.data;
    return {
    
    
      next() {
    
    
        if (index < data.length) {
    
    
          return {
    
     value: data[index++], done: false };
        } else {
    
    
          return {
    
     value: undefined, done: true };
        }
      }
    }
  }
};

// 遍历可迭代对象
for (const item of myIterable) {
    
    
  console.log(item);
  // 输出 1 2 3
}

// 通过迭代器对象遍历可迭代对象
const iterator = myIterable[Symbol.iterator]();
console.log(iterator.next()); // {done: false, value: 1}
console.log(iterator.next()); // {done: false, value: 2}
console.log(iterator.next()); // {done: false, value: 3}
console.log(iterator.next()); // {done: true, value: undefined}

Possibilité d'utiliser for...ofla traversée et ...l'expansion de l'expandeur. Lors de l'utilisation de ces deux méthodes, le moteur js appelle en fait la méthode [Symbol.iterator] de l'objet itérable pour obtenir un itérateur, puis exécute la méthode suivante de l'itérateur pour en obtenir la valeur.

Mais si vous souhaitez utiliser la méthode suivante pour parcourir manuellement, vous devez const myIterable2=createIterator(obj)[Symbol.iterator]()exécuter manuellement la méthode [Symbol.iterator] de cet objet itérable pour obtenir l'itérateur (objet avec la méthode suivante).

1.3, transformer un objet non itérable en itérable

La raison pour laquelle le type d'objet ne peut pas être itéré est que son objet prototype n'a pas l'attribut [Symbol.

// 自定义一个可迭代对象
function createIterator(obj){
    
    
    return {
    
        // 返回一个迭代器对象
        //模仿原生迭代器添加Symbol.iterator方法
        [Symbol.iterator]: function () {
    
     
            const keys=Object.keys(obj)
            let i=0
            return {
    
    
                next:function () {
    
      //迭代器对象一定有next()方法
                    const done = ( i >= keys.length );
                    const key=keys[i]
                    const value = !done ? obj[key] : undefined;
                    i++
                    return {
    
        //next()方法返回结果对象
                        value: value,
                        done: done
                    }
                }
            }
         }
    }
}
const obj={
    
    
    name:'名字',
    age:18

}
const myIterable=createIterator(obj)
// 使用 for-of 循环遍历可迭代对象
for(var v of myIterable){
    
    
    console.log(v)
    //名字
    //18
}
console.log([...myIterable])//['名字',18]
  
const myIterable2=createIterator(obj)[Symbol.iterator]()
console.log(myIterable2.next())//{ value: '名字', done: false }
console.log(myIterable2.next())//{ value: 18, done: false }  
console.log(myIterable2.next())//{ value: undefined, done: true }

Deuxièmement, le générateur Générateur

La plus grande fonction de son existence est de nous aider à générer rapidement des objets/générateurs itérables

Les générateurs en JavaScript sont des fonctions spéciales qui peuvent être utilisées pour définir des itérateurs qui génèrent une séquence de valeurs au moment de l'exécution. La fonction Generator function*commence par et utilise en interne yieldle mot clé pour spécifier chaque valeur générée lors de l'itération du générateur. Chaque fois que yieldle mot clé exécution de la fonction est suspendue et nextl'exécution de la fonction peut être reprise en appelant la fonction méthode sur l'itérateur.

2.1, créer un générateur

function* myGenerator() {
    
    
  yield 1;
  yield 2;
  yield 3;
}

const iter = myGenerator(); // 创建迭代器
for(var v of iter){
    
    
  console.log(v)//1,2,3
}
const iter2 = myGenerator(); // 创建迭代器
console.log(iter2.next()); // { value: 1, done: false }
console.log(iter2.next()); // { value: 2, done: false }
console.log(iter2.next()); // { value: 3, done: false }
console.log(iter2.next()); // { value: undefined, done: true }

console.log(iter === iter[Symbol.iterator]()); // true,对象本身是可迭代对象

La fonction générateur renvoie à la fois un objet itérable (qui peut être utilisé for...of) et un itérateur (qui peut être utilisé directement next()) .

每次yield就会生成一个断点。每次next就执行到这个断点过。

2.2, à la fois un objet itérable et un itérateur

Quelque chose comme ça Symbol.iteratorrevient de lui-même. Il a à la fois Symbol.iteratornext et next, il peut donc renvoyer à la fois un objet itérable et un itérateur.

function myGenerator(arr) {
    
    
  let i=0
  return {
    
    
    next(){
    
    
      const done =  i >= arr.length;
      const value = !done ? arr[i++] : undefined;
      return {
    
        //next()方法返回结果对象
          value: value,
          done: done
      }
    },
    [Symbol.iterator]() {
    
     
      return this
    }
  }
}

const iter = myGenerator([1,2,3,4,5,6]); // 创建迭代器
for(var v of iter){
    
    
  console.log(v)//1,2,3,4,5,6
}
console.log(iter === iter[Symbol.iterator]()); // true,对象本身是可迭代对象
const iter2 = myGenerator([1,2,3,4,5,6,7]); // 创建迭代器
console.log(iter2.next()); // { value: 1, done: false }
console.log(iter2.next()); // { value: 2, done: false }
console.log(iter2.next()); // { value: 3, done: false }
console.log(iter2.next()); // { value: 4, done: false }

Les objets produits par les générateurs ont de telles caractéristiques.

2.3, paramètres de passe suivante

L'expression yield elle-même n'a pas de valeur de retour, ou elle retourne toujours undefined. La méthode suivante peut prendre un paramètre, qui sera utilisé comme valeur de retour de l'expression de rendement précédente. [Notez qu'il s'agit du précédent], c'est-à-dire que le prochain s'arrête au premier rendement, et que le deuxième paramètre d'entrée suivant est la valeur de retour du premier rendement.

C'est-à-dire que le premier paramètre suivant n'a pas de sens, car il n'a pas le rendement du point d'arrêt précédent.

function* foo(x) {
    
    
    let y = x * (yield)
    return y
    console.log("111")//后续的代码不会被执行
    yield 2
}
const it = foo(6)
it.next()//执行到yield过,x*这段语句还未执行
let res = it.next(7)//next传入7作为上一个yield的返回,代码接着执行,计算后赋值给y,然后return结束
console.log(res) // { value: 42, done: true }
it.next(8)

returnforcera le générateur à entrer dans l'état fermé, et la valeur fournie à returnla méthode est la valeur de l'objet itérateur terminé , c'est-à-dire que l'état de l'objet renvoyé à ce moment trueest la valeur transmise.

2.4, la méthode de retour termine le générateur plus tôt

Comme ci-dessus, la méthode de retour forcera également le générateur à entrer dans l'état fermé, et la valeur fournie à returnla méthode est la valeur de l'objet itérateur terminé , c'est-à-dire que l'état de l'objet renvoyé à ce moment trueest la valeur passé dans.

function* foo() {
    
    
    console.log("11")
    yield 1
    console.log("22")
    yield 2
    console.log("33")
    yield 3
    console.log("44")
    yield 4
}
const it = foo()
console.log("next",it.next())//next { value: 1, done: false }
console.log("return",it.return("test"))//return { value: 'test', done: true }

2.5, throw génère une erreur pour terminer le générateur

throw()La méthode injectera une erreur fournie dans l'objet générateur lorsqu'elle est en pause. Si l'erreur n'est pas traitée, le générateur s'arrêtera.

function* foo() {
    
    
    console.log("11")
    yield 1
    console.log("22")
    yield 2
    console.log("33")
    yield 3
    console.log("44")
    yield 4
}
const it = foo()
console.log("next",it.next())//next { value: 1, done: false }
console.log("return",it.throw(new Error('出错了!')))//Error: 出错了!

Une compréhension simple est que les méthodes next(), throw() et return() remplacent toutes yield et ce dernier par des paramètres entrants.

2.6, itération déléguée de l'expression yield*

yield*GeneratorNous permet d'appeler une autre Generatorfonction ou un objet itérable dans une fonction.

Lorsqu'une Generatorfonction atteint une yield*expression, elle suspend l'exécution et transfère l'exécution à une autre Generatorfonction ou itérable. Les droits d'exécution ne reviendront pas à la Generatorfonction .

Ceci est également appelé itération déléguée . De cette façon, plusieurs générateurs peuvent être enchaînés.

function * anotherGenerator(i) {
    
    
    yield i + 1;
    yield i + 2;
    yield i + 3;
}

function * generator(i) {
    
    
    yield* anotherGenerator(i);
    yield "最后一个"
}
var gen = generator(1);
console.log(gen.next().value)//2
console.log(gen.next().value)//3
console.log(gen.next().value)//4
console.log(gen.next().value)//最后一个
for (let value of generator(2)) {
    
    
    console.log(value); // 输出 3,4,5,'最后一个'
}

Troisièmement, l'application de Generator en programmation asynchrone à la naissance d'attend

3.1, comme solution pour la programmation asynchrone

Quand on sait que Generator et yield peuvent être utilisés ensemble pour suspendre l'exécution du code, il faut être parfaitement conscient qu'il peut être utilisé pour résoudre la programmation asynchrone, bien que Promise ait grandement amélioré la lisibilité et la maintenabilité de la programmation asynchrone, en évitant la création de callback hell . Mais dans certains cas, le code deviendra encore très compliqué, notamment lorsque vous devez gérer plusieurs opérations asynchrones en même temps, ou que vous devez contrôler l'ordre de ces opérations asynchrones. L'utilisation de Promise nécessite toujours l'écriture d'un grand nombre d'instructions then pour traiter les résultats des opérations asynchrones, ce qui peut augmenter la complexité du code dans une certaine mesure.

La fonction Générateur, de par son mécanisme d'appel bloquant, permet d'écrire du code asynchrone comme du code synchrone. Voici un exemple de lecture de fichier :

const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
let res1,res2,res3
readFile('test.txt')
.then((res)=>{
    
    
    res1=res
    return readFile('test1.txt')
})
.then((res)=>{
    
    
    res2=res
    return readFile('test2.txt')
})
.then((res)=>{
    
    
    res3=res
    console.log("结果",res1+res2+res3)
})

Réécrire en utilisant la fonction générateur :

const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    
    
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
const generator = myGenerator();
generator.next().value//取得第一个promise对象
.then((result1)=>{
    
    
   return generator.next(result1).value
})//取得第二个promise对象
.then((result2)=>{
    
    
   return generator.next(result2).value
})//取得第三个promise对象
.then((result3)=>{
    
    
    generator.next(result3)//取得最后一个文件的值,继续执行代码
})

Si seulement regarder :

function* myGenerator() {
    
    
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}

Est-ce presque la même chose que la synchronisation ?

Mais il semble que le générateur.next() ci-dessous en ait encore un tas, ce qui est similaire à l'utilisation de promesse auparavant.

Afin de faciliter la compréhension, nous réécrivons le code ci-dessus sous la forme d'un enfer de rappel.


const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    
    
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
const generator = myGenerator();
generator.next().value.then((result1)=>{
    
    
    generator.next(result1).value.then((result2)=>{
    
    
        generator.next(result2).value.then((result3)=>{
    
    
            generator.next(result3)
        })
    })
})

3.2, encapsulant la valeur récursive passant des générateurs

Notez que ce truc passe en fait des valeurs de manière récursive :

generator.next().value.then((result1)=>{
    
    
    generator.next(result1).value.then((result2)=>{
    
    
        generator.next(result2).value.then((result3)=>{
    
    
            generator.next(result3)
        })
    })
})

Eh bien, nous pouvons en fait trouver un moyen de l'encapsuler, donc le code devient comme ceci :

const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    
    
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
//封装函数
function co(generator){
    
    
    const gen=generator();
    function _next(val){
    
    
        var p=gen.next(val);
        if(p.done) return;
        //这里用Promise.resolve是为了避免p.value不是promise,将之转化一下
        Promise.resolve(p.value).then(res=>{
    
    
            _next(res);
        })
    }
    _next()
}
co(myGenerator)

À ce stade, c'est presque la même chose.Il existe des implémentations complexes sur Internet, mais l'encapsulation actuelle nous suffit pour comprendre comment elle convertit l'écriture asynchrone en écriture synchrone.

Quatre, la naissance d'async et d'attendre

Si le moteur js nous aide à introduire la fonction co lors de l'exécution du code et à exécuter co(myGenerator), alors pour nous qui écrivons du code, avons-nous juste besoin d'écrire :

const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    
    
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}

N'est-ce pas presque la même chose que async/wait ?

En d'autres termes, cela peut être compris comme ceci :

当我们写下asycn的时候,js引擎就把它当做一个生成器函数处理,并且帮助我们引入了co函数,并且做好递归传值的工作。
而await就等同于yield。

Veuillez ajouter une description de l'image

Ainsi sont nés async et await, qui nous permettent d'écrire du code asynchrone aussi soyeux que synchrone.

const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
async function test() {
    
    
    //先按照顺序读取三个文件
   const txt1= await readFile('./text1.txt');
   const txt2= await readFile('./text2.txt');
   const txt3= await readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
test()

Je suppose que tu aimes

Origine blog.csdn.net/weixin_42349568/article/details/130256176
conseillé
Classement