One place for hosting & domains

      Nodejs

      Comment installer Node.js sur Ubuntu 18.04


      Introduction

      Node.js est une plateforme JavaScript de programmation à usage général qui permet aux utilisateurs de créer rapidement des applications réseau. En utilisant JavaScript aussi bien en front-end qu’en back-end, Node.js rend le développement plus cohérent et intégré.

      Dans ce guide, nous vous montrerons comment démarrer avec Node.js sur un serveur Ubuntu 18.04.

      Conditions préalables

      Ce guide suppose que vous utilisez Ubuntu 18.04. Avant de commencer, vous devez avoir un compte utilisateur non root avec privilèges sudo sur votre système. Vous pouvez apprendre à créer et configurer ce type de compte en suivant le tutoriel sur la configuration initiale de serveur pour Ubuntu 18.04.

      Installation de la version stable pour Ubuntu

      Ubuntu 18.04 contient une version de Node.js dans ses référentiels par défaut qui peut être utilisée pour assurer une expérience cohérente sur plusieurs systèmes. Au moment de la rédaction de cet article, la version dans les référentiels est la 8.10.0. Il ne s’agit pas de la dernière version, mais elle devrait être stable et capable de permettre une expérience rapide du langage.

      Pour obtenir cette version, vous pouvez utiliser le gestionnaire de package apt. Actualisez votre index de package local en tapant :

      Installez Node.js à partir des référentiels :

      Si le package dans les référentiels répond à vos besoins, c'est tout ce que vous devez faire pour installer Node.js. Dans la plupart des cas, vous voudrez également installer npm, le gestionnaire de package Node.js. Vous pouvez le faire en tapant :

      Cela vous permettra d'installer des modules et des packages à utiliser avec Node.js.

      En raison d'un conflit avec un autre package, l'exécutable des référentiels Ubuntu est appelé nodejs au lieu de node. Gardez cela à l'esprit lorsque vous utilisez un logiciel.

      Pour vérifier quelle version de Node.js vous avez installée après ces premières étapes, tapez :

      Une fois que vous avez établi quelle version de Node.js vous avez installée depuis les référentiels Ubuntu, vous pouvez décider si vous souhaitez ou non travailler avec d'autres versions, archives de packages ou gestionnaires de versions. Nous allons ensuite discuter de ces éléments, ainsi que de méthodes d'installation plus souples et plus robustes.

      Installation à l'aide d'un PPA

      Pour obtenir une version plus récente de Node.js, vous pouvez ajouter le PPA (personal package archive) maintenu par NodeSource. Celui-ci vous permettra de trouver des versions plus récentes de Node.js que les référentiels officiels Ubuntu et de choisir entre Node.js v6.x (supporté jusqu'en avril 2019), Node.js v8.x (la version LTS actuelle, supportée jusqu'en décembre 2019), Node.js v10.x (la deuxième version LTS actuelle, supportée jusqu'en avril 2021) et Node.js v11.x (la version actuelle, supportée jusqu'en juin 2019).

      Tout d'abord, installez le PPA afin d'avoir accès à son contenu. Dans votre répertoire de base, utilisez curl pour récupérer le script d'installation de votre version souhaitée, en veillant à remplacer 10.x par la chaîne de votre version souhaitée (si différente) :

      • cd ~
      • curl -sL https://deb.nodesource.com/setup_10.x -o nodesource_setup.sh

      Vous pouvez inspecter le contenu de ce script avec nano (ou votre éditeur de texte préféré) :

      Exécutez le script sous sudo :

      • sudo bash nodesource_setup.sh

      Le PPA sera ajouté à votre configuration et le cache local de votre package sera automatiquement mis à jour. Après avoir exécuté le script d'installation de Nodesource, vous pouvez installer le package Node.js de la même manière que vous l'avez fait ci-dessus :

      Pour vérifier quelle version de Node.js vous avez installée après ces premières étapes, tapez :

      Output

      v10.14.0

      Le package nodejs contient le binaire nodejs ainsi que npm, vous n'avez donc pas besoin d'installer npm séparément.

      npm utilise un fichier de configuration dans votre répertoire de base pour suivre les mises à jour. Il sera créé la première fois que vous utiliserez npm. Exécutez cette commande pour vérifier que npm est installé et pour créer le fichier de configuration :

      Output

      6.4.1

      Pour que certains packages npm fonctionnent (par exemple, ceux qui nécessitent la compilation du code source), vous devrez installer le package build-essential :

      • sudo apt install build-essential

      Vous disposez maintenant des outils nécessaires pour travailler avec les packages npm qui nécessitent de compiler du code source.

      Installation à l'aide de NVM

      Une alternative à l'installation de Node.js avec apt est d'utiliser le gestionnaire de version Node.js appelé nvm. Au lieu de travailler au niveau du système d'exploitation, nvm travaille au niveau d'un répertoire indépendant au sein de votre répertoire de base. Cela signifie que vous pouvez installer plusieurs versions autonomes de Node.js sans affecter l'ensemble du système.

      Le contrôle de votre environnement avec nvm vous permet d'accéder aux versions les plus récentes de Node.js, et de conserver et gérer les versions précédentes. Il s'agit toutefois d'un utilitaire différent de apt et les versions de Node.js que vous gérez avec lui sont distinctes des versions que vous gérez avec apt.

      Pour télécharger le script d'installation de nvm à partir de la page GitHub du projet, vous pouvez utiliser curl. Veuillez noter que le numéro de version peut différer de celui qui est mis en évidence ici :

      • curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh -o install_nvm.sh

      Inspectez le script d'installation avec nano :

      Exécutez le script avec bash :

      Cela installera le logiciel dans un sous-répertoire de votre répertoire de base dans ~/.nvm. Cela ajoutera également les lignes nécessaires à votre fichier ~/.profile pour utiliser le fichier.

      Pour accéder à la fonctionnalité nvm, vous devez soit vous déconnecter et vous reconnecter, soit charger le fichier ~/.profile afin que votre session actuelle soit informée des modifications :

      Avec nvm installé, vous pouvez installer des versions isolées de Node.js. Pour obtenir des informations sur les versions de Node.js disponibles, tapez :

      Output

      ... v8.11.1 (Latest LTS: Carbon) v9.0.0 v9.1.0 v9.2.0 v9.2.1 v9.3.0 v9.4.0 v9.5.0 v9.6.0 v9.6.1 v9.7.0 v9.7.1 v9.8.0 v9.9.0 v9.10.0 v9.10.1 v9.11.0 v9.11.1 v10.0.0

      Comme vous pouvez le voir, la version LTS actuelle au moment de la rédaction de cet article est la v8.11.1. Vous pouvez l'installer en tapant :

      En général, nvm utilise la dernière version installée. Vous pouvez indiquer à nvm d'utiliser la version que vous venez de télécharger en tapant :

      Lorsque vous installez Node.js en utilisant nvm, l'exécutable est appelé node. Vous pouvez voir la version actuellement utilisée par le shell en tapant :

      Output

      v8.11.1

      Si vous avez plusieurs versions de Node.js, vous pouvez voir celles qui sont installées en tapant :

      Si vous souhaitez utiliser par défaut l'une des versions, tapez :

      Cette version sera automatiquement sélectionnée lors de l'ouverture d'une nouvelle session. Vous pouvez également y faire référence par l'alias comme ceci :

      Chaque version de Node.js assure le suivi de ses propres packages et dispose de npm pour les gérer.

      Vous pouvez également demander à npm d'installer des packages dans le répertoire ./node_modules du projet Node.js. Utilisez la syntaxe suivante pour installer le module express :

      Si vous souhaitez installer le module de manière globale, le rendant ainsi disponible aux autres projets utilisant la même version de Node.js, vous pouvez ajouter l'indicateur -g :

      Cela installera le package dans :

      ~/.nvm/versions/node/node_version/lib/node_modules/express
      

      L'installation globale du module vous permettra d'exécuter des commandes depuis la ligne de commande, mais vous devrez relier le package à votre sphère locale pour le solliciter à partir d'un programme :

      Vous pouvez en savoir plus sur les options disponibles avec nvm en tapant :

      Suppression de Node.js

      Vous pouvez désinstaller Node.js en utilisant apt ou nvm, selon la version que vous souhaitez cibler. Pour supprimer la version stable, vous devez utiliser l'utilitaire apt au niveau du système.

      Pour supprimer la version stable, tapez ce qui suit :

      Cette commande supprimera le package et conservera les fichiers de configuration. Ils peuvent vous être utiles si vous avez l'intention de réinstaller le package ultérieurement. Si vous ne voulez pas conserver les fichiers de configuration pour un usage ultérieur, exécutez ce qui suit :

      Cela désinstallera le package et supprimera les fichiers de configuration qui lui sont associés.

      Enfin, vous pouvez supprimer tous les packages inutilisés qui ont été installés automatiquement avec le package supprimé :

      Pour désinstaller une version de Node.js que vous avez activée à l'aide de nvm, déterminez d'abord si la version que vous souhaitez supprimer est la version active actuelle :

      Si la version que vous ciblez n'est pas la version active actuelle, vous pouvez exécuter :

      • nvm uninstall node_version

      Cette commande désinstallera la version sélectionnée de Node.js.

      Si la version que vous souhaitez supprimer est la version active actuelle, vous devez d'abord désactiver nvm pour permettre les modifications :

      Vous pouvez maintenant désinstaller la version actuelle en utilisant la commande uninstall ci-dessus, qui supprimera tous les fichiers associés à la version ciblée de Node.js, à l'exception des fichiers mis en cache qui peuvent être utilisés pour la réinstallation.

      Conclusion

      Il existe plusieurs façons de faire fonctionner Node.js sur votre serveur Ubuntu 18.04. Votre situation déterminera laquelle des méthodes ci-dessus est la mieux adaptée à vos besoins. L'utilisation de la version packagée dans le référentiel d'Ubuntu est la méthode la plus simple, mais l'utilisation de nvm offre une plus grande flexibilité.



      Source link

      How To Automate Your Node.js Production Deployments with Shipit on CentOS 7


      The author selected the Electronic Frontier Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Shipit is a universal automation and deployment tool for Node.js developers. It features a task flow based on the popular Orchestrator package, login and interactive SSH commands through OpenSSH, and an extensible API. Developers can use Shipit to automate build and deployment workflows for a wide range of Node.js applications.

      The Shipit workflow allows developers to not only configure tasks, but also to specify the order in which they are executed; whether they should be run synchronously or asynchronously and on which environment.

      In this tutorial you will install and configure Shipit to deploy a Node.js application from your local development environment to your production environment. You’ll use Shipit to deploy your application and configure the remote server by:

      • transferring your Node.js application’s files from your local environment to the production environment (using rsync, git, and ssh).
      • installing your application’s dependencies (node modules).
      • configuring and managing the Node.js processes running on the remote server with PM2.

      Prerequisites

      Before you begin this tutorial you’ll need the following:

      Note: Windows users will need to install the Windows Subsystem for Linux to execute the commands in this guide.

      Step 1 — Setting Up the Remote Repository

      Shipit requires a Git repository to synchronize between the local development machine and the remote server. In this step you’ll create a remote repository on Github.com. While each provider is slightly different the commands are somewhat transferrable.

      To create a repository, open Github.com in your web browser and log in. You will notice that in the upper-right corner of any page there is a + symbol. Click +, and then click New repository.

      Github-new-repository

      Type a short, memorable name for your repository, for example, hello-world. Note that whatever name you choose here will be replicated as the project folder that you’ll work from on your local machine.

      Github-repository-name

      Optionally, add a description of your repository.

      Github-repository-description

      Set your repository’s visibility to your preference, either public or private.

      Make sure the repository is initialized with a .gitignore, select Node from the Add .gitignore dropdown list. This step is important to avoid having unnecessary files (like the node_modules folder) being added to your repository.

      Github-gitignore-node

      Click the Create repository button.

      The repository now needs to be cloned from Github.com to your local machine.

      Open your terminal and navigate to the location where you want to store all your Node.js project files. Note that this process will create a sub-folder within the current directory. To clone the repository to your local machine, run the following command:

      • git clone https://github.com/your-github-username/your-github-repository-name.git

      You will need to replace your-github-username and your-github-repository-name to reflect your Github username and the previously supplied repository name.

      Note: If you have enabled two-factor authentication (2FA) on Github.com, you must use a personal access token or SSH key instead of your password when accessing Github on the command line. The Github Help page related to 2FA provides further information.

      You’ll see output similar to:

      Output

      Cloning into 'your-github-repository-name'... remote: Enumerating objects: 3, done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3 Unpacking objects: 100% (3/3), done.

      Move to the repository by running the following command:

      • cd your-github-repository-name

      Inside the repository is a single file and folder, both of which are files used by Git to manage the repository. You can verify this with:

      You’ll see output similar to the following:

      Output

      total 8 0 drwxr-xr-x 4 asciant staff 128 22 Apr 07:16 . 0 drwxr-xr-x 5 asciant staff 160 22 Apr 07:16 .. 0 drwxr-xr-x 13 asciant staff 416 22 Apr 07:16 .git 8 -rw-r--r-- 1 asciant staff 914 22 Apr 07:16 .gitignore

      Now that you have configured a working git repository, you’ll create the shipit.js file that manages your deployment process.

      Step 2 — Integrating Shipit into a Node.js Project

      In this step, you’ll create an example Node.js project and then add the Shipit packages. This tutorial provides an example app—the Node.js web server that accepts HTTP requests and responds with Hello World in plain text. To create the application, run the following command:

      Add the following example application code to hello.js (updating the APP_PRIVATE_IP_ADDRESS variable to your app server’s private network IP address):

      hello.js

      var http = require('http');
      http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('Hello Worldn');
      }).listen(8080, 'APP_PRIVATE_IP_ADDRESS');
      console.log('Server running at http://APP_PRIVATE_IP_ADDRESS:8080/');
      

      Now create your package.json file for your application:

      This command creates a package.json file, which you’ll use to configure your Node.js application. In the next step, you’ll add dependencies to this file with the npm command line interface.

      Output

      Wrote to ~/hello-world/package.json: { "name": "hello-world", "version": "1.0.0", "description": "", "main": index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

      Next, install the necessary npm packages with the following command:

      • npm install --save-dev shipit-cli shipit-deploy shipit-shared

      You use the --save-dev flag here as the Shipit packages are only required on your local machine. You’ll see output similar to the following:

      Output

      + shipit-shared@4.4.2 + shipit-cli@4.2.0 + shipit-deploy@4.1.4 updated 4 packages and audited 21356 packages in 11.671s found 62 low severity vulnerabilities run `npm audit fix` to fix them, or `npm audit` for details

      This also added the three packages to your package.json file as development dependencies:

      package.json

      . . .
        "devDependencies": {
          "shipit-cli": "^4.2.0",
          "shipit-deploy": "^4.1.4",
          "shipit-shared": "^4.4.2"
        },
      . . .
      

      With your local environment configured, you can now move on to preparing the remote app server for Shipit-based deployments.

      Step 3 — Preparing the Remote App Server

      In this step, you’ll use ssh to connect to your app server and install your remote dependency rsync. Rsync is a utility for efficiently transferring and synchronizing files between local computer drives and across networked computers by comparing the modification times and sizes of files.

      Shipit uses rsync to transfer and synchronize files between your local computer and the remote app server. You won’t be issuing any commands to rsync directly; Shipit will handle it for you.

      Note: How To Set Up a Node.js Application for Production on CentOS 7 left you with two servers app and web. These commands should be executed on app only.

      Connect to your remote app server via ssh:

      • ssh deployer@your_app_server_ip

      Install rsync on your server by running the following command:

      Confirm the installation with:

      You’ll see a similar line within the output of this command:

      Output

      rsync version 3.1.2 protocol version 31 . . .

      You can end your ssh session by typing exit.

      With rsync installed and available on the command line, you can move on to deployment tasks and their relationship with events.

      Step 4 — Configuring and Executing Deployment Tasks

      Both events and tasks are key components of Shipit deployments and it is important to understand how they complement the deployment of your application. The events triggered by Shipit represent specific points in the deployment lifecycle. Your tasks will execute in response to these events, based on the sequence of the Shipit lifecycle.

      A common example of where this task/event system is useful in a Node.js application, is the installation of the app’s dependencies (node_modules) on the remote server. Later in this step you’ll have Shipit listen for the updated event (which is issued after the application’s files are transferred) and run a task to install the application’s dependencies (npm install) on the remote server.

      To listen to events and execute tasks, Shipit needs a configuration file that holds information about your remote server (the app server) and registers event listeners and the commands to be executed by these tasks. This file lives on your local development computer, inside your Node.js application’s directory.

      To get started, create this file, including information about your remote server, the event listeners you want to subscribe to, and some definitions of your tasks. Create shipitfile.js within your application root directory on your local machine by running the following command:

      Now that you’ve created a file, it needs to be populated with the initial environment information that Shipit needs. This is primarily the location of your remote Git repository and importantly, your app server’s public IP address and SSH user account.

      Add this initial configuration and update the highlighted lines to match your environment:

      shipitfile.js

      module.exports = shipit => {
        require('shipit-deploy')(shipit);
        require('shipit-shared')(shipit);
      
        const appName = 'hello';
      
        shipit.initConfig({
          default: {
            deployTo: '/home/sammy/your-domain',
            repositoryUrl: 'https://git-provider.tld/YOUR_GIT_USERNAME/YOUR_GIT_REPO_NAME.git',
            keepReleases: 5,
            shared: {
              overwrite: true,
              dirs: ['node_modules']
            }
          },
          production: {
            servers: 'sammy@YOUR_APP_SERVER_PUBLIC_IP'
          }
        });
      
        const path = require('path');
        const ecosystemFilePath = path.join(
          shipit.config.deployTo,
          'shared',
          'ecosystem.config.js'
        );
      
        // Our listeners and tasks will go here
      
      };
      

      Updating the variables in your shipit.initConfig method provides Shipit with configuration specific to your deployment. These represent the following to Shipit:

      • deployTo: is the directory where Shipit will deploy your application’s code to on the remote server. Here you use the /home/ folder for a non-root user with sudo privileges (/home/sammy) as it is secure, and will avoid permission issues. The /your-domain component is a naming convention to distinguish the folder from others in the user’s home folder.
      • repositoryUrl: is the URL to the full Git repository, Shipit will use this URL to ensure the project files are in sync prior to deployment.
      • keepReleases: is the number of releases to keep on the remote server. A release is a date-stamped folder containing your application’s files at the time of release. These can be useful for rollback of a deployment.
      • shared: is configuration that corresponds with keepReleases that allows directories to be shared between releases. In this instance, we have a single node_modules folder that is shared between all releases.
      • production: represents a remote server to deploy your application to. In this instance, you have a single server (app server) that you name production, with the servers: configuration matching your SSH user and public ip address. The name production, corresponds with the Shipit deploy command used toward the end of this tutorial (npx shipit server name deploy or in your case npx shipit production deploy).

      Further information on the Shipit Deploy Configuration object can be found in the Shipit Github repository.

      Before continuing to update your shipitfile.js, let’s review the following example code snippet to understand Shipit tasks:

      Example event listener

      shipit.on('deploy', () => { shipit.start('say-hello'); }); shipit.blTask('say-hello', async () => { shipit.local('echo "hello from your local computer"') });

      This is an example task that uses the shipit.on method to subscribe to the deploy event. This task will wait for the deploy event to be emitted by the Shipit lifecycle, then when the event is received, the task executes the shipit.start method that tells Shipit to start the say-hello task.

      The shipit.on method takes two parameters, the name of the event to listen for and the callback function to execute when the event is received.

      Under the shipit.on method declaration, the task is defined with the shipit.blTask method. This creates a new Shipit task that will block other tasks during its execution (it is a synchronous task). The shipit.blTask method also takes two parameters, the name of the task it is defining and a callback function to execute when the task is triggered by shipit.start.

      Within the callback function of this example task (say-hello), the shipit.local method executes a command on the local machine. The local command echos "hello from your local computer" into the terminal output.

      If you wanted to execute a command on the remote server, you would use the shipit.remote method. The two methods, shipit.local and shipit.remote, provide an API to issue commands either locally, or remotely as part of a deployment.

      Now update the shipitfile.js to include event listeners to subscribe to the Shipit lifecycle with shipit.on. Add the event listeners to your shipitfile.js, inserting them following the comment placeholder from the initial configuration // Our tasks will go here:

      shipitfile.js

      . . .
        shipit.on('updated', () => {
          shipit.start('npm-install', 'copy-config');
        });
      
        shipit.on('published', () => {
          shipit.start('pm2-server');
        });
      

      These two methods are listening for the updated and the published events that are emitted as part of the Shipit deployment lifecycle. When the event is received, they will each initiate tasks using the shipit.start method, similarly to the example task.

      Now that you’ve scheduled the listeners, you’ll add the corresponding task. Add the following task to your shipitfile.js, inserting them after your event listeners:

      shipitfile.js

      . . .
      shipit.blTask('copy-config', async () => {
      
      const fs = require('fs');
      
      const ecosystem = `
      module.exports = {
      apps: [
        {
          name: '${appName}',
          script: '${shipit.releasePath}/hello.js',
          watch: true,
          autorestart: true,
          restart_delay: 1000,
          env: {
            NODE_ENV: 'development'
          },
          env_production: {
            NODE_ENV: 'production'
          }
        }
      ]
      };`;
      
        fs.writeFileSync('ecosystem.config.js', ecosystem, function(err) {
          if (err) throw err;
          console.log('File created successfully.');
        });
      
        await shipit.copyToRemote('ecosystem.config.js', ecosystemFilePath);
      });
      

      You first declare a task called copy-config. This task creates a local file called ecosystem.config.js and then copies that file to your remote app server. PM2 uses this file to manage your Node.js application. It provides the necessary file path information to PM2 to ensure that it is running your latest deployed files. Later in the build process, you’ll create a task that runs PM2 with ecosystem.config.js as configuration.

      If your application needs environment variables (like a database connection string) you can declare them either locally in env: or on the remote server in env_production: in the same manner that you set the NODE_ENV variable in these objects.

      Add the next task to your shipitfile.js following the copy-config task:

      shipitfile.js

      . . .
      shipit.blTask('npm-install', async () => {
        shipit.remote(`cd ${shipit.releasePath} && npm install --production`);
      });
      

      Next, you declare a task called npm-install. This task uses a remote bash terminal (via shipit.remote) to install the app’s dependencies (npm packages).

      Add the last task to your shipitfile.js following the npm-install task:

      shipitfile.js

      . . .
      shipit.blTask('pm2-server', async () => {
        await shipit.remote(`pm2 delete -s ${appName} || :`);
        await shipit.remote(
          `pm2 start ${ecosystemFilePath} --env production --watch true`
        );
      });
      

      Finally you declare a task called pm2-server. This task also uses a remote bash terminal to first stop PM2 from managing your previous deployment through the delete command and then start a new instance of your Node.js server providing the ecosystem.config.js file as a variable. You also let PM2 know that it should be using environment variables from the production block in your initial configuration and you ask PM2 to watch the application, restarting it if it crashes.

      The complete shipitfile.js file:

      shipitfile.js

      module.exports = shipit => {
        require('shipit-deploy')(shipit);
        require('shipit-shared')(shipit);
      
        const appName = 'hello';
      
        shipit.initConfig({
          default: {
            deployTo: '/home/deployer/example.com',
            repositoryUrl: 'https://git-provider.tld/YOUR_GIT_USERNAME/YOUR_GIT_REPO_NAME.git',
            keepReleases: 5,
            shared: {
              overwrite: true,
              dirs: ['node_modules']
            }
          },
          production: {
            servers: 'deployer@YOUR_APP_SERVER_PUBLIC_IP'
          }
        });
      
        const path = require('path');
        const ecosystemFilePath = path.join(
          shipit.config.deployTo,
          'shared',
          'ecosystem.config.js'
        );
      
        // Our listeners and tasks will go here
        shipit.on('updated', async () => {
          shipit.start('npm-install', 'copy-config');
        });
      
        shipit.on('published', async () => {
          shipit.start('pm2-server');
        });
      
        shipit.blTask('copy-config', async () => {
          const fs = require('fs');
          const ecosystem = `
      module.exports = {
        apps: [
          {
            name: '${appName}',
            script: '${shipit.releasePath}/hello.js',
            watch: true,
            autorestart: true,
            restart_delay: 1000,
            env: {
              NODE_ENV: 'development'
            },
            env_production: {
              NODE_ENV: 'production'
            }
          }
        ]
      };`;
      
          fs.writeFileSync('ecosystem.config.js', ecosystem, function(err) {
            if (err) throw err;
            console.log('File created successfully.');
          });
      
          await shipit.copyToRemote('ecosystem.config.js', ecosystemFilePath);
        });
      
        shipit.blTask('npm-install', async () => {
          shipit.remote(`cd ${shipit.releasePath} && npm install --production`);
        });
      
        shipit.blTask('pm2-server', async () => {
          await shipit.remote(`pm2 delete -s ${appName} || :`);
          await shipit.remote(
            `pm2 start ${ecosystemFilePath} --env production --watch true`
          );
        });
      };
      

      Save and exit the file when you’re ready.

      With your shipitfile.js configured, event listeners, and associated tasks finalized you can move on to deploying to the app server.

      Step 5 — Deploying Your Application

      In this step, you will deploy your application remotely and test that the deployment made your application available to the internet.

      Because Shipit clones the project files from the remote Git repository, you need to push your local Node.js application files from your local machine to Github. Navigate to your Node.js project’s application directory (where your hello.js and shiptitfile.js are located) and run the following command:

      The git status command displays the state of the working directory and the staging area. It lets you see which changes have been staged, which haven’t, and which files aren’t being tracked by Git. Your files are untracked and appear red in the output:

      Output

      On branch master Your branch is up to date with 'origin/master'. Untracked files: (use "git add <file>..." to include in what will be committed) hello.js package-lock.json package.json shipitfile.js nothing added to commit but untracked files present (use "git add" to track)

      You can add these files to your repository with the following command:

      This command does not produce any output, although if you were to run git status again, the files would appear green with a note that there are changes to be committed.

      You can create a commit running the following command:

      • git commit -m "Our first commit"

      The output of this command provides some Git-specific information about the files.

      Output

      [master c64ea03] Our first commit 4 files changed, 1948 insertions(+) create mode 100644 hello.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 shipitfile.js

      All that is left now is to push your commit to the remote repository for Shipit to clone to your app server during deployment. Run the following command:

      The output includes information about the synchronization with the remote repository:

      Output

      Enumerating objects: 7, done. Counting objects: 100% (7/7), done. Delta compression using up to 8 threads Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 15.27 KiB | 7.64 MiB/s, done. Total 6 (delta 0), reused 0 (delta 0) To github.com:Asciant/hello-world.git e274312..c64ea03 master -> master

      To deploy your application, run the following command:

      • npx shipit production deploy

      The output of this command (which is too large to include in its entirety) provides detail on the tasks being executed and the result of the specific function. The output following for the pm2-server task shows the Node.js app has been launched:

      Output

      Running 'deploy:init' task... Finished 'deploy:init' after 432 μs . . . Running 'pm2-server' task... Running "pm2 delete -s hello || :" on host "centos-ap-app.asciant.com". Running "pm2 start /home/deployer/example.com/shared/ecosystem.config.js --env production --watch true" on host "centos-ap-app.asciant.com". @centos-ap-app.asciant.com [PM2][WARN] Node 4 is deprecated, please upgrade to use pm2 to have all features @centos-ap-app.asciant.com [PM2][WARN] Applications hello not running, starting... @centos-ap-app.asciant.com [PM2] App [hello] launched (1 instances) @centos-ap-app.asciant.com ┌──────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬─────┬──────────┬──────────┬──────────┐ @centos-ap-app.asciant.com │ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ @centos-ap-app.asciant.com ├──────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼─────┼──────────┼──────────┼──────────┤ @centos-ap-app.asciant.com │ hello │ 0 │ 1.0.0 │ fork │ 4177 │ online │ 0 │ 0s │ 0% │ 4.5 MB │ deployer │ enabled │ @centos-ap-app.asciant.com └──────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴─────┴──────────┴──────────┴──────────┘ @centos-ap-app.asciant.com Use `pm2 show <id|name>` to get more details about an app Finished 'pm2-server' after 5.27 s Running 'deploy:clean' task... Keeping "5" last releases, cleaning others Running "(ls -rd /home/deployer/example.com/releases/*|head -n 5;ls -d /home/deployer/example.com/releases/*)|sort|uniq -u|xargs rm -rf" on host "centos-ap-app.asciant.com". Finished 'deploy:clean' after 1.81 s Running 'deploy:finish' task... Finished 'deploy:finish' after 222 μs Finished 'deploy' [ deploy:init, deploy:fetch, deploy:update, deploy:publish, deploy:clean, deploy:finish ]

      To view your application as a user would, you can enter your website URL your-domain in your browser to access your web server. This will serve the Node.js Application, via reverse proxy, on the app server where your files were deployed.

      You’ll see a Hello World greeting.

      Note: After the first deployment, your Git repository will be tracking a newly created file named ecosystem.config.js. As this file will be rebuilt on each deploy, and may contain compiled application secrets it should be added to the .gitignore file in the application root directory on your local machine prior to your next git commit.

      .gitignore

      . . .
      # ecosystem.config
      ecosystem.config.js
      

      You’ve deployed your Node.js application to your app server, that refers to your new deployment. With everything up and running, you can move on to monitoring your application processes.

      Step 6 — Monitoring Your Application

      PM2 is a great tool for managing your remote processes, but it also provides features to monitor the performance of these application processes.

      Connect to your remote app server via SSH with this command:

      • ssh deployer@your_app_server_ip

      To obtain specific information related to your PM2 managed processes, run the following:

      You’ll see output similar to:

      Output

      ┌─────────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬──────┬───────────┬──────────┬──────────┐ │ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ ├─────────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼──────┼───────────┼──────────┼──────────┤ │ hello │ 0 │ 0.0.1 │ fork │ 3212 │ online │ 0 │ 62m │ 0.3% │ 45.2 MB │ deployer │ enabled │ └─────────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴──────┴───────────┴──────────┴──────────┘

      You’ll see a summary of the information PM2 has collected. To see detailed information, you can run:

      The output expands on the summary information provided by the pm2 list command. It also provides information on a number of ancillary commands and provides log file locations:

      Output

      Describing process with id 0 - name hello ┌───────────────────┬─────────────────────────────────────────────────────────────┐ │ status │ online │ │ name │ hello │ │ version │ 1.0.0 │ │ restarts │ 0 │ │ uptime │ 82s │ │ script path │ /home/deployer/example.com/releases/20190531213027/hello.js │ │ script args │ N/A │ │ error log path │ /home/deployer/.pm2/logs/hello-error.log │ │ out log path │ /home/deployer/.pm2/logs/hello-out.log │ │ pid path │ /home/deployer/.pm2/pids/hello-0.pid │ │ interpreter │ node │ │ interpreter args │ N/A │ │ script id │ 0 │ │ exec cwd │ /home/deployer │ │ exec mode │ fork_mode │ │ node.js version │ 4.2.3 │ │ node env │ production │ │ watch & reload │ ✔ │ │ unstable restarts │ 0 │ │ created at │ 2019-05-31T21:30:48.334Z │ └───────────────────┴─────────────────────────────────────────────────────────────┘ Revision control metadata ┌──────────────────┬────────────────────────────────────────────────────┐ │ revision control │ git │ │ remote url │ N/A │ │ repository root │ /home/deployer/example.com/releases/20190531213027 │ │ last update │ 2019-05-31T21:30:48.559Z │ │ revision │ 62fba7c8c61c7769022484d0bfa46e756fac8099 │ │ comment │ Our first commit │ │ branch │ master │ └──────────────────┴────────────────────────────────────────────────────┘ Divergent env variables from local env ┌───────────────────────────┬───────────────────────────────────────┐ │ XDG_SESSION_ID │ 15 │ │ HOSTNAME │ N/A │ │ SELINUX_ROLE_REQUESTED │ │ │ TERM │ N/A │ │ HISTSIZE │ N/A │ │ SSH_CLIENT │ 44.222.77.111 58545 22 │ │ SELINUX_USE_CURRENT_RANGE │ │ │ SSH_TTY │ N/A │ │ LS_COLORS │ N/A │ │ MAIL │ /var/mail/deployer │ │ PATH │ /usr/local/bin:/usr/bin │ │ SELINUX_LEVEL_REQUESTED │ │ │ HISTCONTROL │ N/A │ │ SSH_CONNECTION │ 44.222.77.111 58545 209.97.167.252 22 │ └───────────────────────────┴───────────────────────────────────────┘ . . .

      PM2 also provides an in-terminal monitoring tool, accessible with:

      The output of this command is an interactive dashboard, where pm2 provides realtime process information, logs, metrics, and metadata. This dashboard may assist in monitoring resources and error logs:

      Output

      ┌─ Process list ────────────────┐┌─ Global Logs ─────────────────────────────────────────────────────────────┐ │[ 0] hello Mem: 22 MB ││ │ │ ││ │ │ ││ │ └───────────────────────────────┘└───────────────────────────────────────────────────────────────────────────┘ ┌─ Custom metrics (http://bit.l─┐┌─ Metadata ────────────────────────────────────────────────────────────────┐ │ Heap Size 10.73 ││ App Name hello │ │ Heap Usage 66.14 ││ Version N/A │ │ Used Heap Size 7.10 ││ Restarts 0 │ │ Active requests 0 ││ Uptime 55s │ │ Active handles 4 ││ Script path /home/asciant/hello.js │ │ Event Loop Latency 0.70 ││ Script args N/A │ │ Event Loop Latency p95 ││ Interpreter node │ │ ││ Interpreter args N/A │ └───────────────────────────────┘└───────────────────────────────────────────────────────────────────────────┘

      With an understanding of how you can monitor your processes with PM2, you can move on to how Shipit can assist in rolling back to a previous working deployment.

      End your ssh session on your app server by running exit.

      Step 7 — Rolling Back a Bugged Deployment

      Deployments occasionally expose unforeseen bugs, or issues that cause your site to fail. The developers and maintainers of Shipit have anticipated this and have provided the ability for you to roll back to the previous (working) deployment of your application.

      To ensure your PM2 configuration persists, add another event listener to shipitfile.js on the rollback event:

      shipitfile.js

      . . .
        shipit.on('rollback', () => {
          shipit.start('npm-install', 'copy-config');
        });
      

      You add a listener to the rollback event to run your npm-install and copy-config tasks. This is needed because unlike the published event, the updated event is not run by the Shipit lifecycle when rolling back a deployment. Adding this event listener ensures your PM2 process manager points to the most recent deployment, even in the event of a rollback.

      This process is similar to deploying, with a minor change in command. To try rolling back to a previous deployment you can execute the following:

      • npx shipit production rollback

      Like the deploy command, rollback provides details on the roll back process and the tasks being executed:

      Output

      Running 'rollback:init' task... Get current release dirname. Running "if [ -h /home/deployer/example.com/current ]; then readlink /home/deployer/example.com/current; fi" on host "centos-ap-app.asciant.com". @centos-ap-app.asciant.com releases/20190531213719 Current release dirname : 20190531213719. Getting dist releases. Running "ls -r1 /home/deployer/example.com/releases" on host "centos-ap-app.asciant.com". @centos-ap-app.asciant.com 20190531213719 @centos-ap-app.asciant.com 20190531213519 @centos-ap-app.asciant.com 20190531213027 Dist releases : ["20190531213719","20190531213519","20190531213027"]. Will rollback to 20190531213519. Finished 'rollback:init' after 3.96 s Running 'deploy:publish' task... Publishing release "/home/deployer/example.com/releases/20190531213519" Running "cd /home/deployer/example.com && if [ -d current ] && [ ! -L current ]; then echo "ERR: could not make symlink"; else ln -nfs releases/20190531213519 current_tmp && mv -fT current_tmp current; fi" on host "centos-ap-app.asciant.com". Release published. Finished 'deploy:publish' after 1.8 s Running 'pm2-server' task... Running "pm2 delete -s hello || :" on host "centos-ap-app.asciant.com". Running "pm2 start /home/deployer/example.com/shared/ecosystem.config.js --env production --watch true" on host "centos-ap-app.asciant.com". @centos-ap-app.asciant.com [PM2][WARN] Node 4 is deprecated, please upgrade to use pm2 to have all features @centos-ap-app.asciant.com [PM2][WARN] Applications hello not running, starting... @centos-ap-app.asciant.com [PM2] App [hello] launched (1 instances) @centos-ap-app.asciant.com ┌──────────┬────┬─────────┬──────┬──────┬────────┬─────────┬────────┬─────┬──────────┬──────────┬──────────┐ @centos-ap-app.asciant.com │ App name │ id │ version │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ @centos-ap-app.asciant.com ├──────────┼────┼─────────┼──────┼──────┼────────┼─────────┼────────┼─────┼──────────┼──────────┼──────────┤ @centos-ap-app.asciant.com │ hello │ 0 │ 1.0.0 │ fork │ 4289 │ online │ 0 │ 0s │ 0% │ 4.5 MB │ deployer │ enabled │ @centos-ap-app.asciant.com └──────────┴────┴─────────┴──────┴──────┴────────┴─────────┴────────┴─────┴──────────┴──────────┴──────────┘ @centos-ap-app.asciant.com Use `pm2 show <id|name>` to get more details about an app Finished 'pm2-server' after 5.55 s Running 'deploy:clean' task... Keeping "5" last releases, cleaning others Running "(ls -rd /home/deployer/example.com/releases/*|head -n 5;ls -d /home/deployer/example.com/releases/*)|sort|uniq -u|xargs rm -rf" on host "centos-ap-app.asciant.com". Finished 'deploy:clean' after 1.82 s Running 'rollback:finish' task... Finished 'rollback:finish' after 615 μs Finished 'rollback' [ rollback:init, deploy:publish, deploy:clean, rollback:finish ]

      You have configured Shipit to keep 5 releases through the keepReleases: 5 configuration in shipitfile.js. Shipit keeps track of these releases internally to ensure it is able to roll back when required. Shipit also provides a handy way to identify the releases by creating a directory named as a timestamp (YYYYMMDDHHmmss - Example: /home/deployer/your-domain/releases/20190420210548).

      If you wanted to further customize the roll back process, you can listen for events specific to the roll back operation. You can then use these events to execute tasks that will complement your roll back. You can refer to the event list provided in the breakdown of the Shipit lifecycle and configure the tasks/listeners within your shipitfile.js.

      The ability to roll back means that you can always serve a functioning version of your application to your users even if a deployment introduces unexpected bugs/issues.

      Conclusion

      In this tutorial, you configured a workflow that allows you to create a highly customizable alternative to Platform as a Service, all from a couple of servers. This workflow allows for customized deployment and configuration, process monitoring with PM2, the potential to scale and add services, or additional servers or environments to the deployment when required.

      If you are interested in continuing to develop your Node.js skills, check out the DigtalOcean Node.js content as well as the How To Code in Node.js Series.



      Source link

      Написание асинхронного кода в Node.js


      Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Во многих программах JavaScript код выполняется так, как написал его разработчик — строка за строкой. Это называется синхронным выполнением, поскольку строки выполняются друг за другом в порядке, в котором они были написаны. Однако не все инструкции, которые вы передаете компьютеру, должны быть приняты сразу. Например, если вы отправите сетевой запрос, процесс выполнения вашего кода вынужден ждать, пока данные будут возвращены, прежде чем можно будет продолжить работу. В данном случае время будет потрачено зря, если другой код не будет выполняться, пока вы ждете завершения выполнения запроса. Чтобы решить эту проблему, разработчики используют асинхронное программирование, в котором строки кода выполняются в ином порядке, чем в том, в каком они написаны. С помощью асинхронного программирования мы можем выполнять другой код, ожидая завершения длительных действий, например сетевых запросов.

      Код JavaScript выполняется в одном потоке внутри процесса компьютера. Его код выполняется синхронно в этом потоке по одной инструкции в один момент времени. Поэтому, если мы выполняем длительную по времени задачу в потоке, весь остальной код блокируется, пока задача не будет выполнена. Используя функции асинхронного программирования JavaScript, мы можем передать длительные по времени задачи в фоновый режим, чтобы избежать этой проблемы. После завершения задачи код, который нам требуется для обработки данных задачи, возвращается в основной поток.

      В этом обучающем руководстве вы узнаете, как JavaScript управляет асинхронными задачами с помощью цикла событий, конструкции JavaScript, которая выполняет новую задачу, пока выполняется другая. Затем вы создадите программу, которая использует асинхронное программирование, чтобы запрашивать список фильмов из Studio Ghibli API, и сохраняет данные в файл CSV. Асинхронный код будет написан тремя разными способами: обратные вызовы, обещания и ключевые слова async/await.

      Примечание. На момент написания данной статьи асинхронное программирование больше не использует исключительно обратные вызовы, но изучение этого устаревшего метода позволяет получить более широкое понимание того, почему сообщество JavaScript сейчас использует обещания. Ключевые слова async/await позволяют нам использовать обещания менее многословным образом, поэтому они являются стандартом для асинхронного программирования в JavaScript на момент написания этой статьи.

      Предварительные требования

      Цикл событий

      Начнем с изучения внутренних процессов выполнения функции JavaScript. Понимание того, как протекают эти процессы, позволит вам писать асинхронный код более осознанно и поможет поддерживать код в будущем.

      Так как интерпретатор JavaScript выполняет код, все функции, вызываемые JavaScript, добавляются в стек вызовов. Стек вызовов — это стек, структура данных в виде списка, где элементы могут быть добавлены только сверху и удалены только сверху. Стек опирается на принцип «последний пришел, первый ушел» (или LIFO). Если вы добавили два элемента в стек, последний добавленный элемент удаляется в первую очередь.

      Давайте проиллюстрируем использование стека вызовов примером. Если JavaScript замечает вызов функции functionA(), она добавляется в стек вызовов. Если эта функция functionA() вызывает другую функцию functionB(), тогда функция functionB() добавляется сверху стека вызовов. Когда JavaScript завершает выполнение функции, она удаляется из стека вызовов. Поэтому JavaScript выполняет функцию functionB() в первую очередь, удаляет ее из стека, а затем заканчивает выполнение functionA() и удаляет ее из стека вызовов. Поэтому вложенные функции всегда выполняются перед внешними функциями.

      Когда JavaScript выполняет асинхронную операцию, например запись в файл, она добавляется в таблицу в памяти. Эта таблица хранит операцию, условие ее завершения и функцию, которая будет вызываться после ее завершения. После завершения операции JavaScript добавляет связанную функцию в очередь сообщений. Очередь — это другая структура данных в виде списка, где элементы могут быть добавлены только в конец очереди, а удаляются только из начала очереди. В очереди сообщений, если две или более асинхронные операции готовы к выполнению своих функций, асинхронная операция, которая была выполнена в первую очередь, будет иметь функцию, которая помечена на выполнение также в первую очередь.

      Функции в очереди сообщений ожидают добавления в стек вызовов. Цикл событий — это бесконечный процесс, который проверяет, есть ли в стеке вызовов записи. Если записи отсутствуют, первый элемент в очереди сообщений передается в стек вызовов. JavaScript присваивает приоритет функциям в очереди сообщений согласно вызовам функции, которые обнаруживает в коде. Совокупный эффект стека вызовов, очереди сообщений и цикла событий позволяет обрабатывать код JavaScript при управлении асинхронными действиями.

      Теперь, когда вы познакомились с циклом событий, вы понимаете, как будет выполняться асинхронный код, который вы пишете. Обладая этими знаниями, вы можете создать асинхронный код с помощью трех разных подходов: обратные вызовы, обещания и async/await.

      Асинхронное программирование с обратными вызовами

      Функция обратного вызова — это функция, которая передается в качестве аргумента в другую функцию, а затем выполняется после завершения выполнения другой функции. Мы используем обратный вызов, чтобы убедиться, что код выполняется только после завершения асинхронной операции.

      В течение длительного времени обратный вызов был самым распространенным механизмом написания асинхронного кода, но сегодня он считается устаревшим, поскольку затрудняет чтение кода. На этом шаге вы напишите пример асинхронного кода, используя обратные вызовы, чтобы использовать его в качестве исходной точки и продемонстрировать рост эффективности при использовании других стратегий.

      Существует множество способов, позволяющих использовать обратные вызовы функции внутри другой функции. Обычно они имеют следующую структуру:

      function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {
          [ Action ]
      }
      

      Хотя синтаксис JavaScript или Node.js не требует, чтобы функция обратного вызова использовалась в качестве последнего аргумента внешней функции, это стандартная практика, которая упрощает определение обратных вызовов. Также, как правило, разработчики JavaScript используют анонимную функцию в качестве обратного вызова. Анонимные функции — это функции, которые не имеют имени. Обычно код читается гораздо лучше, когда функция определяется в конце списка аргументов.

      Чтобы продемонстрировать использование обратного вызова, давайте создадим модуль Node.js, который пишет список фильмов Studio Ghibli в файл. Во-первых, создайте папку, которая будет хранить файл JavaScript и вывод:

      Затем откройте эту папку:

      Начнем с создания запроса HTTP для Studio Ghibli API, где наша функция обратного вызова будет хранить результаты. Для этого мы установим библиотеку, которая позволяет нам получить доступ к данным HTTP-ответа в обратном вызове.

      В терминале инициализируйте npm, чтобы мы могли получить ссылку на наши пакеты позднее:

      Затем установите библиотеку request:

      Теперь откройте новый файл с именем callbackMovies.js в текстовом редакторе, например nano:

      В текстовом редакторе добавьте следующий код. Начнем с отправки запроса HTTP с помощью модуля request:

      callbackMovies.js

      const request = require('request');
      
      request('https://ghibliapi.herokuapp.com/films');
      

      В первой строке мы загрузим модуль request, который был установлен с помощью npm. Модуль возвращает функцию, которая может выполнять запросы HTTP; затем мы сохраним эту функцию в константе request.

      Затем мы создадим запрос HTTP с помощью функции request(). Теперь мы распечатаем данные из запроса HTTP в консоль, добавив выделенные изменения:

      callbackMovies.js

      const request = require('request');
      
      request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
          if (error) {
              console.error(`Could not send request to API: ${error.message}`);
              return;
          }
      
          if (response.statusCode != 200) {
              console.error(`Expected status code 200 but received ${response.statusCode}.`);
              return;
          }
      
          console.log('Processing our list of movies');
          movies = JSON.parse(body);
          movies.forEach(movie => {
              console.log(`${movie['title']}, ${movie['release_date']}`);
          });
      });
      

      При использовании функции request() мы передадим в нее два параметра:

      • URL-адрес сайта, который мы запрашиваем
      • Функция обратного вызова, которая обрабатывает любые ошибки или успешные ответы на выполненные запросы

      Наша функция обратного вызова имеет три аргумента: error, response и body. Когда запрос HTTP выполняется, аргументам автоматически присваиваются значения в зависимости от вывода. Если не удалось выполнить запрос, тогда error будет содержать объект, а response и body будут иметь значение null. Если запрос будет выполнен успешно, HTTP-ответ сохраняется в качестве значения аргумента response. Если наш HTTP-ответ возвращает данные (в данном примере мы получим JSON), данные сохранятся в качестве значения аргумента body.

      Наша функция обратного вызова сначала проверяет, была ли получена ошибка. Рекомендуется выполнять проверку на ошибки в обратном вызове в первую очередь, чтобы исполнение обратного вызова не продолжалось с неполными данными. В данном случае мы регистрируем ошибку и исполнение функции. Затем мы проверяем код состояния ответа. Наш сервер может не всегда быть доступным, а API могут менять, в результате чего когда-то рабочие запросы становятся неверными. Проверив, что код статуса равен 200, что означает, что запрос выполнен успешно, мы можем убедиться, что наш ответ будет выглядеть так, как мы ожидаем.

      В заключение мы спарсим тело ответа в массив и пробежимся по каждому фильму, чтобы записать его название и год выпуска.

      После сохранения и закрытия файла запустите этот скрипт с помощью следующей команды:

      Результат будет выглядеть следующим образом:

      Output

      Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014

      Мы успешно получили список фильмов Studio Ghibli с годом премьеры каждого фильма. Теперь давайте завершим работу программы, добавив список фильмов, который мы сейчас сохранили, в файл.

      Обновите файл callbackMovies.js в текстовом редакторе, чтобы включить следующий выделенный код, который создает файл CSV с данными нашего фильма:

      callbackMovies.js

      const request = require('request');
      const fs = require('fs');
      
      request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
          if (error) {
              console.error(`Could not send request to API: ${error.message}`);
              return;
          }
      
          if (response.statusCode != 200) {
              console.error(`Expected status code 200 but received ${response.statusCode}.`);
              return;
          }
      
          console.log('Processing our list of movies');
          movies = JSON.parse(body);
          let movieList = '';
          movies.forEach(movie => {
              movieList += `${movie['title']}, ${movie['release_date']}n`;
          });
      
          fs.writeFile('callbackMovies.csv', movieList, (error) => {
              if (error) {
                  console.error(`Could not save the Ghibli movies to a file: ${error}`);
                  return;
              }
      
              console.log('Saved our list of movies to callbackMovies.csv');;
          });
      });
      

      Если внимательно изучить внесенные изменения, можно увидеть, что мы импортируем модуль fs. Этот модуль является стандартным модулем для всех установок Node.js и содержит метод writeFile(), который осуществляет асинхронную запись в файл.

      Вместо того, чтобы выводить данные в консоль, мы добавим их в строковую переменную movieList. Затем мы воспользуемся writeFile() для сохранения содержимого movieList в новый файл callbackMovies.csv. В заключение мы передадим обратный запрос в функцию writeFile(), которая имеет только один аргумент: error. Это позволит нам обрабатывать случаи, когда выполнить запись в файл не удается, например, когда пользователь, с помощью которого мы выполняем запуск процесса node, не имеет необходимых разрешений.

      Сохраните файл и еще раз запустите нашу программу Node.js с помощью следующей команды:

      В папке ghibliMovies вы увидите файл callbackMovies.csv, который содержит следующее:

      callbackMovies.csv

      Castle in the Sky, 1986
      Grave of the Fireflies, 1988
      My Neighbor Totoro, 1988
      Kiki's Delivery Service, 1989
      Only Yesterday, 1991
      Porco Rosso, 1992
      Pom Poko, 1994
      Whisper of the Heart, 1995
      Princess Mononoke, 1997
      My Neighbors the Yamadas, 1999
      Spirited Away, 2001
      The Cat Returns, 2002
      Howl's Moving Castle, 2004
      Tales from Earthsea, 2006
      Ponyo, 2008
      Arrietty, 2010
      From Up on Poppy Hill, 2011
      The Wind Rises, 2013
      The Tale of the Princess Kaguya, 2013
      When Marnie Was There, 2014
      

      Необходимо отметить, что мы осуществляем запись в файл CSV в обратном вызове запроса HTTP. Так как код находится в функции обратного вызова, он будет осуществлять запись в файл только после выполнения запроса HTTP. Если мы хотим подключиться к базе данных после создания нашего файла CSV, нужно создать другую асинхронную функцию, которая будет вызываться в обратном вызове writeFile(). Чем более асинхронный код нам нужен, тем больше функций обратного вызова нам нужно добавить.

      Давайте представим, что мы хотим выполнять пять асинхронных операций, каждая из которых может выполняться только после завершения предыдущей. Если бы нам нужно было написать код с таким функционалом, у нас получилось бы примерно следующее:

      doSomething1(() => {
          doSomething2(() => {
              doSomething3(() => {
                  doSomething4(() => {
                      doSomething5(() => {
                          // final action
                      });
                  });
              });
          });
      });
      

      Когда вложенные обратные вызовы содержат много строк кода, они становятся значительно более сложными для понимания и чтения. По мере увеличения размера и сложности вашего JavaScript-проекта, данный эффект становится все более заметным до тех пор, пока в конце концов станет невозможно управлять этой конструкцией. По этой причине разработчики больше не используют обратные вызовы для обработки асинхронных операций. Чтобы улучшить синтаксис нашего асинхронного кода, мы можем использовать обещания.

      Использование обещаний и ответственное асинхронное программирование

      Обещание — это объект JavaScript, который будет возвращать значение в какой-то момент в будущем. Асинхронные функции могут возвращать объекты обещаний вместо конкретных значений. Если мы получим значение в будущем, то скажем, что обещание было выполнено. Если в будущем мы получим ошибку, то можем сказать, что обещание было отклонено. Во всех остальных случаях обещание обрабатывается в состоянии ожидания.

      Обещания обычно имеют следующую форму:

      promiseFunction()
          .then([ Callback Function for Fulfilled Promise ])
          .catch([ Callback Function for Rejected Promise ])
      

      Как показано в этом шаблоне, обещания также используют функции обратного вызова. У нас есть функция обратного вызова для метода then(), который выполняется при выполнении обещания. Также у нас есть функция обратного вызова для метода catch() для обработки любых ошибок, возникающих при исполнении обещания.

      Давайте получим практический опыт работы с обещаниями, переписав нашу программу Studio Ghibli, на этот раз с использованием обещаний.

      Axios — это HTTP-клиент JavaScript, работающий с обещаниями, так что давайте попробуем его установить:

      Теперь в любом текстовом редакторе на ваш выбор создайте новый файл promiseMovies.js:

      Наша программа выполняет запрос HTTP с помощью axios и затем использует специальную версию fs на базе обещаний, чтобы сохранить новый файл CSV.

      Введите этот код в файл promiseMovies.js, чтобы загрузить Axios и отправить запрос HTTP в API фильмов:

      promiseMovies.js

      const axios = require('axios');
      
      axios.get('https://ghibliapi.herokuapp.com/films');
      

      В первой строке мы загрузим модуль axios, сохранив возвращаемую функцию в константе с именем axios. Затем мы используем метод axios.get() для отправки запроса HTTP для API.

      Метод axios.get() возвращает обещание. Давайте используем это обещание, чтобы вывести список фильмов Ghibli в консоль:

      promiseMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      
      axios.get('https://ghibliapi.herokuapp.com/films')
          .then((response) => {
              console.log('Successfully retrieved our list of movies');
              response.data.forEach(movie => {
                  console.log(`${movie['title']}, ${movie['release_date']}`);
              });
          })
      

      Давайте разберем, что здесь происходит. После выполнения HTTP-запроса GET с помощью axios.get() мы используем функцию then(), которая выполняется только при выполнении обещания. В данном случае мы выводим фильмы на экран, как мы делали в примере с обратным вызовом.

      Чтобы улучшить эту программу, добавьте выделенный код для записи данных HTTP в файл:

      promiseMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      
      axios.get('https://ghibliapi.herokuapp.com/films')
          .then((response) => {
              console.log('Successfully retrieved our list of movies');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
      
              return fs.writeFile('promiseMovies.csv', movieList);
          })
          .then(() => {
              console.log('Saved our list of movies to promiseMovies.csv');
          })
      

      Еще раз дополнительно импортируем модуль fs. Обратите внимание, как после импорта fs мы получили .promises. Node.js включает работающую на базе обещаний версию библиотеки fs на базе обратного вызова, так что при использовании в старых проектах обратная совместимость не пострадает.

      Первая функция then(), которая обрабатывает запрос HTTP, теперь вызывает fs.writeFile() вместо вывода в консоль. Поскольку мы импортировали версию fs на базе обещаний, наша функция writeFile() возвращает другое обещание. Фактически мы добавили другую функцию then() для случаев, когда обещание writeFile() выполняется.

      Обещание может возвращать новое обещание, что позволяет нам выполнять обещания одно за другим. Это, в свою очередь, дает нам возможность реализовать несколько асинхронных операций. Это называется цепочкой обещаний и является аналогом вложенных функций обратного вызова. Вторая функция then() вызывается только после успешной записи в файл.

      Примечание. В данном примере мы не проверяли код состояния HTTP, как мы делали в примере обратного вызова. По умолчанию axios не выполняет свое обещание при получении кода состояния, указывающего на ошибку. Т. е. нам больше не нужно выполнять эту проверку.

      Чтобы выполнить эту программу, свяжите обещание с функцией catch(), как показано в выделенном фрагменте ниже:

      promiseMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      
      axios.get('https://ghibliapi.herokuapp.com/films')
          .then((response) => {
              console.log('Successfully retrieved our list of movies');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
      
              return fs.writeFile('promiseMovies.csv', movieList);
          })
          .then(() => {
              console.log('Saved our list of movies to promiseMovies.csv');
          })
          .catch((error) => {
              console.error(`Could not save the Ghibli movies to a file: ${error}`);
          });
      

      Если какое-либо обещание в цепи обещаний не выполняется, JavaScript автоматически переходит в функцию catch(), если она определена. Именно поэтому у нас есть только один вызов catch(), хотя мы используем две асинхронные операции.

      Давайте подтвердим, что наша программа генерирует аналогичный результат, выполнив запуск:

      В папке ghibliMovies вы увидите файл promiseMovies.csv содержащий следующее:

      promiseMovies.csv

      Castle in the Sky, 1986
      Grave of the Fireflies, 1988
      My Neighbor Totoro, 1988
      Kiki's Delivery Service, 1989
      Only Yesterday, 1991
      Porco Rosso, 1992
      Pom Poko, 1994
      Whisper of the Heart, 1995
      Princess Mononoke, 1997
      My Neighbors the Yamadas, 1999
      Spirited Away, 2001
      The Cat Returns, 2002
      Howl's Moving Castle, 2004
      Tales from Earthsea, 2006
      Ponyo, 2008
      Arrietty, 2010
      From Up on Poppy Hill, 2011
      The Wind Rises, 2013
      The Tale of the Princess Kaguya, 2013
      When Marnie Was There, 2014
      

      С обещаниями мы можем писать более сжатый и понятный код, по сравнению с обратными вызовами. Цепь обещаний с обратными вызовами представляет собой более понятный вариант, чем вложенные обратные вызовы. Однако при создании большого количества асинхронных вызовов наша цепочка обещаний становится более сложной и трудно поддерживаемой.

      Большое количество кода при использовании обратных вызовов и обещаний вызвано необходимостью создания функций при получении результата асинхронной задачи. Более надежным способом будет ожидание асинхронного результата и его помещение в переменную за пределами функции. Так мы можем использовать результаты в переменных, не создавая функцию. Мы можем добиться этого с помощью ключевых слов async и await.

      Использование async и await в JavaScript

      Ключевые слова async/await предоставляют возможность использования альтернативного синтаксиса при работе с обещаниями. Вместо получения результата обещания в методе then() результат возвращается в качестве значения, как в любой другой функции. Мы определяем функцию с ключевым словом async, чтобы указать JavaScript, что это асинхронная функция, возвращающая обещание. Мы используем ключевое слово await, чтобы указать JavaScript возвращать результаты обещания вместо возвращения самого обещания при его выполнении.

      Использование async/await выглядит следующим образом:

      async function() {
          await [Asynchronous Action]
      }
      

      Давайте посмотрим, как использование async/await может улучшить нашу программу Studio Ghibli. Воспользуйтесь текстовым редактором для создания и открытия нового файла asyncAwaitMovies.js:

      В новом файле JavaScript мы сначала импортируем те же самые модули, которые мы использовали при работе с обещаниями:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      

      Импорт выполняется аналогично импорту в promiseMovies.js, поскольку async/await используют обещания.

      Теперь мы используем слово async для создания функции с нашим асинхронным кодом:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {}
      

      Мы создадим новую функцию с именем saveMovies(), но добавим async в начале ее определения. Это важно, поскольку мы можем использовать ключевое слово await только в асинхронной функции.

      Используйте ключевое слово await для создания запроса HTTP, получающего список фильмов из Ghibli API:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          let response = await axios.get('https://ghibliapi.herokuapp.com/films');
          let movieList = '';
          response.data.forEach(movie => {
              movieList += `${movie['title']}, ${movie['release_date']}n`;
          });
      }
      

      В функции saveMovies() мы создаем запрос HTTP с помощью axios.get(), как и прежде. На этот раз мы не соединяем ее с функцией then(). Вместо этого мы добавим await перед ее вызовом. Когда JavaScript видит await, выполняется остальной код функции только после того,как axios.get() завершит выполнение и задаст переменную response. Остальной код сохраняет данные фильмов, чтобы мы могли записывать их в файл.

      Давайте запишем данные в файл:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          let response = await axios.get('https://ghibliapi.herokuapp.com/films');
          let movieList = '';
          response.data.forEach(movie => {
              movieList += `${movie['title']}, ${movie['release_date']}n`;
          });
          await fs.writeFile('asyncAwaitMovies.csv', movieList);
      }
      

      Мы также используем ключевое слово await при записи в файл с помощью fs.writeFile().

      Для выполнения этой функции нам нужно поймать ошибки, которые могут генерировать наши обещания. Давайте выполним это, обернув наш код в блок try/catch:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          try {
              let response = await axios.get('https://ghibliapi.herokuapp.com/films');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
              await fs.writeFile('asyncAwaitMovies.csv', movieList);
          } catch (error) {
              console.error(`Could not save the Ghibli movies to a file: ${error}`);
          }
      }
      
      

      Поскольку обещания могут не выполняться, мы поместим наш асинхронный код внутри блока try/catch. Он будет перехватывать любые ошибки, которые будут генерироваться при невозможности выполнения запроса HTTP или записи в файл.

      В заключение мы вызовем нашу асинхронную функцию saveMovies(), которая будет выполняться при запуске программы с помощью node.

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          try {
              let response = await axios.get('https://ghibliapi.herokuapp.com/films');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
              await fs.writeFile('asyncAwaitMovies.csv', movieList);
          } catch (error) {
              console.error(`Could not save the Ghibli movies to a file: ${error}`);
          }
      }
      
      saveMovies();
      

      На первый взгляд это типичный блок синхронного кода JavaScript. Он имеет меньшее количество функций, передаваемых через него, и выглядит аккуратнее. Эти небольшие улучшения облегчают поддержку асинхронного кода с async/await.

      Проверьте эту версию нашей программы, запустив ее в терминале:

      В папке ghibliMovies будет создан новый файл asyncAwaitMovies.csv со следующим содержанием:

      asyncAwaitMovies.csv

      Castle in the Sky, 1986
      Grave of the Fireflies, 1988
      My Neighbor Totoro, 1988
      Kiki's Delivery Service, 1989
      Only Yesterday, 1991
      Porco Rosso, 1992
      Pom Poko, 1994
      Whisper of the Heart, 1995
      Princess Mononoke, 1997
      My Neighbors the Yamadas, 1999
      Spirited Away, 2001
      The Cat Returns, 2002
      Howl's Moving Castle, 2004
      Tales from Earthsea, 2006
      Ponyo, 2008
      Arrietty, 2010
      From Up on Poppy Hill, 2011
      The Wind Rises, 2013
      The Tale of the Princess Kaguya, 2013
      When Marnie Was There, 2014
      

      Теперь вы используете функции JavaScript async/await для управления асинхронным кодом.

      Заключение

      В этом обучающем руководстве вы узнали, как JavaScript обрабатывает функции и управляет асинхронными операциями с помощью цикла событий. Затем мы написали программы, которые создают файл CSV после отправки запроса HTTP для получения данных о фильмах с использованием различных асинхронных техник программирования. Сначала мы использовали устаревший подход с обратными вызовами. Затем мы использовали обещания и, наконец, поработали с async/await, чтобы сделать синтаксис обещаний более сжатым.

      Обладая пониманием асинхронного кода в Node.js, мы можем разрабатывать программы, которые могут использовать преимущества асинхронного программирования, например программы, опирающиеся на вызовы API. Посмотрите на этот список публичных API. Для их использования вам нужно выполнять асинхронные запросы HTTP, как мы делали в этом обучающем руководстве. Для дальнейшего изучения попробуйте создать приложение, использующее эти API, чтобы попрактиковаться с описанными здесь техниками.



      Source link