web-dev-qa-db-fra.com

Les promesses imbriquées sont-elles normales dans node.js?

Le problème avec lequel je me débattais depuis deux semaines lors de l’apprentissage de node.js est de savoir comment faire de la programmation synchrone à l’aide de node. J'ai trouvé que peu importe comment j'essaie de faire les choses séquentiellement je finis toujours avec des promesses imbriquées. J'ai constaté qu'il existe des modules tels que Q pour aider à l'enchaînement des promesses en ce qui concerne la facilité de maintenance.

Ce que je ne comprends pas en faisant de la recherche, c'est Promise.all(), Promise.resolve() et Promise.reject(). Promise.reject est assez explicite par son nom, mais lors de l'écriture d'une application, je ne comprends pas comment inclure ces éléments dans des fonctions ou des objets sans perturber le comportement de l'application.

Lorsque vous utilisez un langage de programmation tel que Java ou C #), il y a définitivement une courbe d'apprentissage pour node.js. La question qui subsiste est de savoir si l'enchaînement des promesses est normal (meilleure pratique) dans node.js. .

Exemple:

driver.get('https://website.com/login').then(function () {
    loginPage.login('company.admin', 'password').then(function () {
        var employeePage = new EmployeePage(driver.getDriver());

        employeePage.clickAddEmployee().then(function() {
            setTimeout(function() {
                var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

                addEmployeeForm.insertUserName(employee.username).then(function() {
                    addEmployeeForm.insertFirstName(employee.firstName).then(function() {
                        addEmployeeForm.insertLastName(employee.lastName).then(function() {
                            addEmployeeForm.clickCreateEmployee().then(function() {
                                employeePage.searchEmployee(employee);
                            });
                        });
                    });
                });
            }, 750);
        });
    });
});
56
Grim

Non, l'un des grands avantages de Promises est que vous pouvez garder votre code asynchrone linéaire plutôt que imbriqué (rappel du style de passage de continuation).

Les promesses vous donnent des instructions de retour et une erreur, que vous perdez avec le style de continuation.

Vous devez renvoyer la promesse de vos fonctions asynchrones pour pouvoir chaîner la valeur renvoyée.

Voici un exemple:

driver.get('https://website.com/login')
  .then(function() {
    return loginPage.login('company.admin', 'password')
  })
  .then(function() {
    var employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
  })
  .then(function() {
    setTimeout(function() {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

      addEmployeeForm.insertUserName(employee.username)
        .then(function() {
          return addEmployeeForm.insertFirstName(employee.firstName)
        })
        .then(function() {
          return addEmployeeForm.insertLastName(employee.lastName)
        })
        .then(function() {
          return addEmployeeForm.clickCreateEmployee()
        })
        .then(function() {
          return employeePage.searchEmployee(employee)
        });
    }, 750);
});

Promise.all Accepte un tableau de promesses et le résout une fois que toutes les promesses ont été résolues. Le cas échéant, le tableau est rejeté. Cela vous permet d'exécuter du code asynchrone simultanément plutôt qu'en série, tout en attendant le résultat de toutes les fonctions simultanées. Si vous êtes à l'aise avec un modèle fileté, pensez à créer des threads, puis à les rejoindre.

Exemple:

addEmployeeForm.insertUserName(employee.username)
    .then(function() {
        // these two functions will be invoked immediately and resolve concurrently
        return Promise.all([
            addEmployeeForm.insertFirstName(employee.firstName),
            addEmployeeForm.insertLastName(employee.lastName)
        ])
    })
    // this will be invoked after both insertFirstName and insertLastName have succeeded
    .then(function() {
        return addEmployeeForm.clickCreateEmployee()
    })
    .then(function() {
        return employeePage.searchEmployee(employee)
    })
    // if an error arises anywhere in the chain this function will be invoked
    .catch(function(err){
        console.log(err)
    });

Promise.resolve() et Promise.reject() sont des méthodes utilisées lors de la création d'un Promise. Ils sont habitués à encapsuler une fonction asynchrone à l'aide de rappels afin que vous puissiez utiliser Promises au lieu de rappels.

Résoudre résoudra/tiendra la promesse (cela signifie qu'une méthode chaînée then sera appelée avec la valeur résultante).
Reject refusera la promesse (cela signifie que toute méthode chainée then ne sera pas appelée, mais que la première méthode chaînée catch sera appelé avec l'erreur qui est survenue).

J'ai laissé votre setTimeout en place pour préserver le comportement de vos programmes, mais c'est probablement inutile.

91
Tate Thurston

Utilisez la bibliothèque async et utilisez async.series À la place des chaînages imbriqués, ce qui est vraiment moche et difficile à déboguer/comprendre.

async.series([
    methodOne,
    methodTwo
], function (err, results) {
    // Here, results is the value from each function
    console.log(results);
});

La méthode Promise.all(iterable) renvoie une promesse qui est résolue lorsque toutes les promesses de l'argument itérable ont été résolues ou sont rejetées avec le motif de la première promesse passée qui a été rejetée.

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); // [3, 1337, "foo"] 
});

La méthode Promise.resolve(value) renvoie un objet Promise résolu avec la valeur donnée. Si la valeur est une thenable (c’est-à-dire une méthode then), la promesse retournée "suivra" cette thenable, en adoptant son état éventuel; sinon la promesse retournée sera remplie avec la valeur.

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

8
Thalaivar

J'ai enlevé l'imbrication inutile. Ill utilise la syntaxe de 'bluebird' (ma bibliothèque Promise préférée) http://bluebirdjs.com/docs/api-reference.html

var employeePage;

driver.get('https://website.com/login').then(function() {
    return loginPage.login('company.admin', 'password');
}).then(function() {
    employeePage = new EmployeePage(driver.getDriver());    
    return employeePage.clickAddEmployee();
}).then(function () {
    var deferred = Promise.pending();
    setTimeout(deferred.resolve,750);
    return deferred.promise;
}).then(function() {
    var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
    return Promise.all([addEmployeeForm.insertUserName(employee.username),
                        addEmployeeForm.insertFirstName(employee.firstName),
                        addEmployeeForm.insertLastName(employee.lastName)]);
}).then(function() {
    return addEmployeeForm.clickCreateEmployee();
}).then(function() {
    return employeePage.searchEmployee(employee);
}).catch(console.log);

J'ai modifié votre code pour inclure des exemples pour toutes vos questions.

  1. Il n'est pas nécessaire d'utiliser la bibliothèque asynchrone lorsque vous travaillez avec des promesses. Les promesses sont très puissantes en elles-mêmes et je pense que c'est un anti-modèle pour mélanger des promesses et des bibliothèques comme async.

  2. Normalement, vous devriez éviter d'utiliser le style var deferred = Promise.pending () ... sauf si

'lors de l'encapsulation d'une API de rappel qui ne respecte pas la convention standard. Comme setTimeout: '

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

Pour l'exemple de setTimeout, créez une promesse "différée" ... résolvez la promesse dans setTimeout puis renvoyez-la en dehors de setTimeout. Cela peut sembler un peu peu intuitif. Regardez cet exemple, j'ai répondu à une autre question. promesse Q.js avec noeud. Gestionnaire d'erreur manquant sur `socket`. TypeError: impossible d'appeler la méthode 'then' of non définie

Normalement, vous pouvez utiliser Promise.promisify (someFunction) pour convertir une fonction de type rappel en une fonction de retour Promise.

  1. Promise.all Permet de dire que vous passez plusieurs appels vers un service qui renvoie de manière asynchrone. S'ils ne dépendent pas les uns des autres, vous pouvez passer les appels simultanément.

Il suffit de passer les appels de fonction sous forme de tableau. Promise.all ([promiseReturningCall1, promesseReturningCall2, promesseReturningCall3]);

  1. Enfin, ajoutez un bloc catch à la toute fin..pour vous assurer de détecter toute erreur. Cela interceptera n'importe quelle exception n'importe où dans la chaîne.
3

Je viens de répondre à un question similaire où j'ai expliqué une technique qui utilise des générateurs pour aplatir les chaînes de promesse de manière agréable. La technique tire son inspiration des coroutines.

Prenez ce morceau de code

Promise.prototype.bind = Promise.prototype.then;

const coro = g => {
  const next = x => {
    let {done, value} = g.next(x);
    return done ? value : value.bind(next);
  }
  return next();
};

En l’utilisant, vous pouvez transformer votre chaîne de promesse profondément imbriquée en cette

coro(function* () {
  yield driver.get('https://website.com/login')
  yield loginPage.login('company.admin', 'password');
  var employeePage = new EmployeePage(driver.getDriver());
  yield employeePage.clickAddEmployee();
  setTimeout(() => {
    coro(function* () {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
      yield addEmployeeForm.insertUserName(employee.username);
      yield addEmployeeForm.insertFirstName(employee.firstName);
      yield addEmployeeForm.insertLastName(employee.lastName);
      yield addEmployeeForm.clickCreateEmployee();
      yield employeePage.searchEmployee(employee);
    }());
  }, 750);
}());

En utilisant des générateurs nommés, nous pouvons rendre cela encore plus lisible

// don't forget to assign your free variables
// var driver = ...
// var loginPage = ...
// var employeePage = new EmployeePage(driver.getDriver());
// var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
// var employee = ...

function* createEmployee () {
  yield addEmployeeForm.insertUserName(employee.username);
  yield addEmployeeForm.insertFirstName(employee.firstName);
  yield addEmployeeForm.insertLastName(employee.lastName);
  yield addEmployeeForm.clickCreateEmployee();
  yield employeePage.searchEmployee(employee);
}

function* login () {
  yield driver.get('https://website.com/login')
  yield loginPage.login('company.admin', 'password');
  yield employeePage.clickAddEmployee();
  setTimeout(() => coro(createEmployee()), 750);
}

coro(login());

Cependant, cela ne fait qu'effleurer la surface de ce qui est possible en utilisant des coroutines pour contrôler le flux des promesses. Lisez la réponse que j'ai liée ci-dessus et qui illustre certains des autres avantages et capacités de cette technique.

Si vous avez l’intention d’utiliser des routines à cette fin, je vous encourage à consulter le co library .

J'espère que cela t'aides.

[~ # ~] ps [~ # ~] ne sais pas pourquoi vous utilisez setTimeout de cette façon. Quel est le point d’attente de 750 ms en particulier?

2
user633183

Votre prochaine étape consiste à passer du nid au chaînage. Vous devez comprendre que chaque promesse est une promesse isolée qui peut être enchaînée à une promesse parentale. En d'autres termes, vous pouvez aplatir les promesses d'une chaîne. Chaque résultat de promesse peut être transmis à la suivante.

Voici un excellent article de blog à ce sujet: Aplatissement des chaînes de promesses . Il utilise Angular mais vous pouvez l'ignorer et voir comment une imbrication profonde de promesses se transforme en une chaîne.

Une autre bonne réponse est ici, sur StackOverflow: Comprendre les promesses javascript, les piles et le chaînage .

1
Cymen

Vous pouvez enchaîner des promesses comme ceci:

driver.get('https://website.com/login').then(function () {
    return loginPage.login('company.admin', 'password')
)}.then(function () {
    var employeePage = new EmployeePage(driver.getDriver());

    return employeePage.clickAddEmployee().then(function() {
        setTimeout(function() {
            var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
        return addEmployeeForm.insertUserName(employee.username).then(function() {
                retun addEmployeeForm.insertFirstName(employee.firstName)
         }).then(function() {
                return addEmployeeForm.insertLastName(employee.lastName)
         }).then(function() {
             return addEmployeeForm.clickCreateEmployee()
         }).then(function () {
             retrun employeePage.searchEmployee(employee);
        })}, 750);
});

}); });

0
tubu13