Rui  Silva

Rui Silva

1658201400

Melhore A Programação Assíncrona Com Promessas De JavaScript

Se você já escreveu código JavaScript assíncrono antes, já tem uma ideia sobre o uso de retornos de chamada e os problemas com eles. Um grande problema com o uso de retornos de chamada é a possibilidade de se deparar com um inferno de retorno de chamada.

No ES2015, as promessas de JavaScript foram adicionadas à especificação da linguagem, trazendo uma mudança totalmente nova na forma como o código assíncrono é escrito e também mitigando o problema de entrar no inferno de retorno de chamada. Se você estiver usando a sintaxe ES2015 em seu código, talvez já esteja familiarizado com promessas.

Neste guia, você aprenderá algumas maneiras práticas de melhorar a programação assíncrona em JavaScript usando promessas.

Nota: Este guia não é de forma alguma uma introdução às promessas do JavaScript. Algum conhecimento prévio de promessas é necessário para ler este guia.

Criando promessas

Uma promessa JavaScript pode ser criada usando o Promiseconstrutor. O construtor recebe uma executorfunção como seu argumento, que é executado imediatamente para criar a promessa.

O executor, por sua vez, pode receber como argumentos duas funções de callback que podem ser invocadas dentro da função executora para liquidar a promessa, a saber:

  • resolvepor cumprir a promessa com um valor
  • rejectpor rejeitar a promessa com um motivo (geralmente um erro)

Aqui está uma promessa JavaScript muito simples:

const isLessThan10 = (num) => {
  new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!");
    }
  })
    .then((res) => console.log(res))
    .catch((err) => console.log(err));
};

isLessThan10(14); 

Se você executar o código acima, verá "Wrong!!" em seu console, o que significa que a promessa foi obtida rejected. Isso porque 14obviamente não é menor que 10, mas quando você passa um número menor que 10, a promessa será fulfilled.

Estados de promessa

Na seção acima, você deve ter notado o uso de duas palavras: rejectede fulfilled. Esses são dois dos três estados de uma promessa JavaScript. Vamos falar sobre os três estados possíveis de uma promessa.

 

  • rejected– uma promessa é rejeitada quando a operação falha, por exemplo, acima na isLessThan10função, quando passamos 14, a promessa foi rejeitada
  • fulfilled– uma promessa é cumprida quando a operação funciona ou está correta, por exemplo, na função acima, passar um número menor que 10 cumpre a promessa
  • Pending– uma promessa está pendente quando está esperando para ser resolvida ou rejeitada. Uma promessa só chega a esse estado quando a operação é assíncrona

Uma promessa só é cumprida quando é resolvida usando um argumento de resolução de promessa. promise.resolvecumpre uma promessa com um valor, enquanto uma promessa é rejeitada com o argumento de rejeição de promessa. Esses dois estados mostram que a promessa foi liquidada e não está mais pendente.

Promessas liquidadas

Muitas vezes, você só quer criar uma promessa que já está resolvida – cumprida com um valor ou rejeitada com um motivo. Para casos como esse, os métodos Promise.resolve()e são úteis. Promise.reject()Aqui está um exemplo simples:

// This promise is already fulfilled with a number (100)
const fulfilledPromise = Promise.resolve(100);

// This promise is already rejected with an error
const rejectedPromise = Promise.reject(new Error('Operation failed.'));

// Getting the rsolved value of the promise
fulfilledPromise.then(res => console.log(res));

// Getting catching to see the error of the promise
rejectedPromise.then(res => console.log(res)).catch(err => console.log(err.message));

Também pode haver momentos em que você não tem certeza se um valor é uma promessa ou não. Em casos como esse, você pode usar Promise.resolve()para criar uma promessa cumprida com o valor e depois trabalhar com a promessa retornada. Aqui está um exemplo:

// User object
const USER = {
  name: 'Glad Chinda',
  country: 'Nigeria',
  job: 'Fullstack Engineer'
};

// Create a fulfilled promise using Promise.resolve()
Promise.resolve(USER)
  .then(user => console.log(user.name));

Manipulando promessas

Uma promessa estabelecida pode ser tratada passando retornos de chamada para os métodos then(), catch(), ou finally()da promessa, como visto acima em alguns trechos de código anteriores. Aqui, vamos refatorar a isLessThan10função e ver como lidar com promessas rejeitadas e cumpridas.

const isLessThan10 = (num) => {
  return new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!!");
    }
  })
};

// Case1
isLessThan10(1)
  .then(console.log)
  .catch(console.error);

// Case2
// Alternatively, the rejection can be handled in the same .then() call
// By passing the rejection handler as second argument to .then()
isLessThan10(12).then(console.log, console.error);

Além de usar .catch()para lidar com promessas rejeitadas, como visto acima, também podemos passar dois retornos de chamada para .then(). O primeiro irá lidar com a promessa se for cumprida, enquanto o outro irá lidar com ela se for rejeitada. Também podemos manipular o valor resolvido da promessa no then()bloco.

.finally()é sempre executado uma vez que a promessa é liquidada, independentemente de ser cumprida ou rejeitada. É um bom lugar para realizar ações de limpeza, como redefinir uma variável ou limpar um estado.

const isLessThan10 = (num) => {
  return new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!!");
    }
  })
    .then(111)
    .catch(222);
};

isLessThan10(11)
  .then((res) => console.log(res))
  .catch((err) => console.error(err))
  .finally(() => console.log("This promise is finally settled!"));

Manipuladores burrosthen

O .then()método pode receber até duas funções de manipulador como seus argumentos: manipulador de cumprimento e manipulador de rejeição.

No entanto, se algum desses dois argumentos não for uma função, .then()substituirá esse argumento por uma função e continuará com o fluxo de execução normal. Torna-se importante saber por qual tipo de função o argumento é substituído. Aqui está o que é:

  • Se o argumento do handler de cumprimento não for uma função, ele será substituído por uma Função de Identidade. Uma função identidade é uma função que simplesmente retorna o argumento que recebe
  • Se o argumento do manipulador de rejeição não for uma função, ele será substituído por uma Função Thrower. Uma função de lançamento é uma função que simplesmente lança o erro ou valor que recebe como seu argumentoAs funções identidade e lançadora escritas

Aqui está um exemplo simples:

const isLessThan10 = (num) => {
  return new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!!");
    }
  })
    .then(111) // Just a random number 
    .catch(222); // Just a random number
};

//This will log 'Correct' to the console
isLessThan10(3).then(res => console.log(res)).catch(err => console.error(err));

// This will log 'Wrong' to the console
isLessThan10(13).then(res => console.log(res)).catch(err => console.error(err));

Se você observar com atenção, notará que nem a identityfunção nem a throwerfunção alteram o fluxo de execução normal da sequência de promessa. Eles simplesmente têm o mesmo efeito de omitir essa .then()chamada específica na cadeia de promessas. Por esse motivo, geralmente me refiro a esses argumentos do manipulador como “manipuladores burros”.

.then()manipuladores sempre retornam promessas

Uma coisa importante a entender sobre o .then()método promise é que ele sempre retorna uma promessa.

Aqui está um detalhamento de como .then()retorna uma promessa com base no que é retornado da função do manipulador passada para ela:

 

handlerFn().então()
nadapromessa que se resolve comundefined
qualquer valorpromessa que é resolvida com o valor retornado
lança um erropromessa que é rejeitada com o erro
promessa já resolvidapromessa que é resolvida para o valor da promessa do manipulador retornada
promessa já rejeitadapromessa que é rejeitada com o valor da promessa do manipulador retornada
outra promessa pendentepromessa cuja resolução/rejeição depende da resolução/rejeição da promessa do manipulador retornada

ver raw then-promise.md hospedado❤pelo GitHub

 

Tempo com promessas

Atrasando a execução

As promessas podem ser muito úteis para aplicações de temporização. Algumas linguagens de programação como o PHP possuem uma sleep()função que pode ser usada para atrasar a execução de uma operação até depois do tempo de espera.

Embora uma sleep()função não exista como parte da especificação JavaScript, as funções global setTimeout()e setInterval()são comumente usadas para executar operações baseadas em tempo.

O setInterval()método é uma função JavaScript usada para executar um bloco de código em um tempo especificado com atrasos entre cada chamada, enquanto o setTimeout()método é usado para adicionar um temporizador a um bloco de código JavaScript.

Aqui está como a sleep()função pode ser simulada usando promessas em JavaScript. No entanto, nesta versão da sleep()função, o tempo de parada será em milissegundos em vez de segundos :

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

Aqui está uma versão ligeiramente expandida e anotada da sleep(ms)função:

const sleep = ms => {
  // Return a new promise
  // No need defining the executor function with a `reject` callback
  return new Promise(resolve => {
    // Pass resolve as the callback to setTimeout
    // This will execute `resolve()` after `ms` milliseconds
    setTimeout(resolve, ms);
  });
}

A sleep(ms)função pode ainda ser melhorada para se tornar uma delayfunção independente que executa uma callbackfunção após o tempo de suspensão especificado.

Aqui está como usar a sleep()função pode ser:

// Sleep for 5 seconds
// Then execute the operation
sleep(5000).then(executeOperation);

// Delay function
// Using async/await with sleep()
const delay = async (callback, seconds = 1) => {
  // Sleep for the specified seconds
  // Then execute the operation
  await sleep(seconds * 1000);
  callback();
}

// Using the `delay()` function
// Execution delayed by 5 seconds
delay(executeOperation, 5);

Medindo o tempo de execução

E se você estiver interessado em saber quanto tempo levou para que uma operação assíncrona fosse concluída? Este é geralmente o caso ao comparar o desempenho de alguma forma de implementação ou funcionalidade.

Aqui está uma implementação simples que aproveita uma promessa JavaScript para calcular o tempo de execução de uma operação assíncrona.

const timing = callback => {
  // Get the start time using performance.now()
  const start = performance.now();

  // Perform the asynchronous operation
  // Finally, log the time difference
  return Promise.resolve(callback())
    .finally(() => console.log(`Timing: ${performance.now() - start}`));
}

Nesta implementação, performance.now()é usado em vez de Date.now()obter o timestamp com uma resolução mais alta. Para ambientes sem navegador onde o performanceobjeto não existe, você pode recorrer usando Date.now()ou outras implementações de host.

No bloco de código abaixo, a timing()função pode ser usada para registrar o tempo de execução de uma operação assíncrona no console:

// Async operation that takes between 1 - 5 seconds
const asyncOperation = () => new Promise(resolve => {
  setTimeout(() => resolve('DONE'), Math.ceil(Math.random() * 5) * 1000);
});

// Compute execution time in ms
// And log it to the console
timing(asyncOperation); // Timing: 4003.4000000014203

Execução sequencial com promessas

Com promessas de JavaScript, você pode executar operações assíncronas em sequência . Isso geralmente ocorre quando uma operação assíncrona posterior depende da execução de uma operação assíncrona anterior ou quando o resultado de uma operação assíncrona anterior é necessário para uma operação posterior.

A execução de operações assíncronas em sequência geralmente envolve o encadeamento de um ou mais .``then()manipuladores .catch()a uma promessa. Quando uma promessa é rejeitada na cadeia, ela é tratada pelo manipulador de rejeição definido no próximo .then()manipulador da cadeia e, em seguida, a execução continua na cadeia.

No entanto, se nenhum manipulador de rejeição tiver sido definido no próximo .then()manipulador da cadeia, a rejeição da promessa será cascata na cadeia até atingir o primeiro .catch()manipulador.

Estudo de caso: aplicativo de galeria de fotos

Digamos que você esteja criando um aplicativo de galeria de fotos e queira buscar fotos de um repositório de fotos online e filtrá-las por formato, proporção, intervalos de dimensão etc.

Aqui estão algumas funções possíveis que você pode ter em seu aplicativo:

/**
 * Fetches photos from the Picsum API
 * @returns {Promise} A promise that is fulfilled with an array of photos from the Picsum repository
 */

const fetchPhotos = () =>
  fetch('https://picsum.photos/list')
    .then(response => response.json());

/**
 * Filters photos and returns only JPEG photos 
 * @param {Array} photos
 * @returns {Array} An array of JPEG photos
 */
const jpegOnly = photos =>
  photos.filter(({ format }) => format.toLowerCase() === 'jpeg')

/**
 * Filters photos and returns only square photos
 * @param {Array} photos
 * @returns {Array} An array of square photos
 */

const squareOnly = photos =>
  photos.filter(({ width, height }) => height && Number.isFinite(height) && (width / height) === 1)

/**
 * Returns a function for filtering photos by size based on `px`
 * @param {number} px The maximum allowed photo dimension in pixels
 * @returns {Function} Function that filters photos and returns an array of photos smaller than `px`
 */

const smallerThan = px => photos =>
  photos.filter(({ width, height }) => Math.max(width, height) < px)

/**
 * Return an object containing the photos count and URLs.
 * @param {Array} photos
 * @returns {Object} An object containing the photos count and URLs
 */

const listPhotos = photos => ({
  count: photos.length,
  photos: photos.map(({ post_url }) => post_url)
})

No bloco de código acima, a fetchPhotos()função busca uma coleção de fotos da API Picsum Photos usando a função global fetch()fornecida pela API Fetch e retorna uma promessa que é cumprida com uma coleção de fotos.

Aqui está a aparência da coleção retornada da API Picsum Photos :

A coleção retornada pela API Picsum Photos

As funções de filtro aceitam uma coleção de fotos como argumento e filtram a coleção das seguintes maneiras:

  • jpegOnly()  — filtra uma coleção de fotos e retorna uma subcoleção de apenas imagens JPEG
  • squareOnly()  — filtra uma coleção de fotos e retorna uma subcoleção apenas de fotos com proporção quadrada
  • smallerThan()  — esta é uma função de ordem superior que obtém uma dimensão e retorna uma função de filtro de fotos que retorna uma subcoleção de fotos cujas dimensões máximas são menores que o limite de dimensão especificado

Digamos que queremos executar esta sequência de operações:

  1. Buscar a coleção de fotos
  2. Filtre a coleção deixando apenas fotos JPEG
  3. Filtre a coleção deixando apenas fotos com proporção quadrada
  4. Filtre a coleção deixando apenas fotos menores que 2500px
  5. Extraia a contagem de fotos e URLs da coleção
  6. Registre a saída final no console
  7. Registre o erro no console se ocorrer um erro em qualquer ponto da sequência

O trecho de código a seguir mostra como podemos encadear a execução dessas operações em uma sequência de promessa:

// Execute asynchronous operations in sequence
fetchPhotos()
  .then(jpegOnly)
  .then(squareOnly)
  .then(smallerThan(2500))
  .then(listPhotos)
  .then(console.log)
  .catch(console.error);

O código acima produzirá um resultado semelhante à imagem abaixo:

A saída da nossa sequência de operações assíncronas

Executando e executando promessas JavaScript em paralelo

Com as promessas do JavaScript, você pode executar várias operações assíncronas independentes em lotes ou em paralelo usando o Promise.all()método.

Promise.all()aceita um iterável de promessas como seu argumento e retorna uma promessa que é cumprida quando todas as promessas do iterável são cumpridas ou é rejeitada quando uma das promessas do iterável é rejeitada.

Se a promessa retornada for cumprida, ela será cumprida com uma matriz de todos os valores das promessas cumpridas no iterável (na mesma ordem). No entanto, se for rejeitado, será rejeitado por causa da primeira promessa no iterável que foi rejeitada.

Estudo de caso: Temperaturas atuais

Digamos que você esteja criando um aplicativo meteorológico que permite aos usuários ver as temperaturas atuais de uma lista de cidades selecionadas.

Usando Promise.all(), você pode fazer uma GETsolicitação à API de clima para buscar a temperatura de todas as cidades selecionadas de uma só vez, para que seus usuários não vejam os dados renderizados um após o outro em seu aplicativo.

O snippet de código a seguir demonstra como buscar as temperaturas atuais das cidades selecionadas em paralelo com Promise.all().

O serviço da API OpenWeatherMap será usado para buscar os dados meteorológicos, então se você quiser acompanhar, vá até o site deles seguindo esse link e inscreva-se para obter uma chave de API.

// Use your OpenWeatherMap API KEY
// Set the current weather data API URL
const API_KEY = 'YOUR_API_KEY_HERE';
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;

// Set the list of cities
const CITIES = [
  'London', 'Tokyo', 'Melbourne', 'Vancouver',
  'Lagos', 'Berlin', 'Paris', 'Johannesburg',
  'Chicago', 'Mumbai', 'Cairo', 'Beijing'
];

const fetchTempForCity = city => {
  return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
    .then(response => response.json())
    .then(data => [ city, data.main.temp || null ]);
}

const fetchTempForCities = cities => {
  return Promise.all(cities.map(fetchTempForCity))
    .then(temps => {
      return temps.reduce((data, [ city, temp ]) => {
        return { ...data, [city]: Number.isFinite(temp) ? temp.toFixed(2) * 1 : null };
      }, {});
    });
}

fetchTempForCities(CITIES)
  .then(console.log, console.error);

No bloco de código acima, criamos as funções abaixo:

  • fetchTempForCity() — aceita uma única cidade como seu argumento e retorna uma promessa que é cumprida com a temperatura atual da cidade especificada (em °C) chamando o serviço da API OpenWeatherMap. A promessa retornada é cumprida com uma matriz do formato:[city, temperature]
  • fetchTempForCities()— aceita uma matriz de cidades e busca a temperatura atual de cada cidade aproveitando Array.prototype.map()para chamar a fetchTempForCity()função em cada cidade.

O Promise.all()método é usado para executar as requisições em paralelo e acumular seus dados em um único array, que, por sua vez, é reduzido a um objeto usando uma Array.prototype.reduce()função .

O trecho de código acima retornará um objeto semelhante ao resultado abaixo:

Os resultados do snippet de código da API OpenWeather

Tratamento de rejeição

É importante observar que, se qualquer uma das promessas de temperatura de busca passadas Promise.all()for rejeitada com um motivo, todo o lote da promessa será rejeitado imediatamente com o mesmo motivo.

Ou seja, se pelo menos uma das doze promessas de temperatura de busca for rejeitada por algum motivo, todo o lote da promessa será rejeitado e, portanto, nenhuma temperatura será retornada da promessa.

O cenário descrito acima geralmente não é o comportamento desejado na maioria dos casos — uma busca de temperatura com falha não deve fazer com que os resultados das buscas bem-sucedidas no lote sejam descartados. Podemos corrigir isso facilmente usando outro método de promessa promise.allSettled(), sobre o qual falaremos abaixo, mas também há outra solução simples.

A solução simples para isso é anexar um .catch()manipulador à fetchTempForCitypromessa, fazendo com que ela cumpra a promessa com um valor de temperatura nulo em casos de rejeição.

Isto é o que vai parecer:

const fetchTempForCity = city => {
  return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
    .then(response => response.json())
    .then(data => [ city, data.main.temp || null ])

    // Attach a `.catch()` handler for graceful rejection handling
    .catch(() => [ city, null ]);
}

Com essa pequena alteração na fetchTempForCity()função, agora há uma garantia muito alta de que a promessa retornada nunca será rejeitada nos casos em que a solicitação falha ou algo dá errado. Em vez disso, ele será preenchido com um array do formato: [city, null], como o abaixo:

A cidade, formatação nula
Com essa alteração, torna-se possível melhorar ainda mais o código para poder agendar novas tentativas para buscas de temperatura com falha.

O trecho de código a seguir inclui algumas adições que podem ser feitas ao código anterior para tornar isso possível.

// An object that will contain the current temperatures of the cities

// The keys are the city names, while the values are their current temperatures (in °C)

let TEMPS = null;

// The maximum number of retries for failed temperature fetches

const MAX_TEMP_FETCH_RETRIES = 5;

// Fetches the current temperatures of multiple cities (in °C) and update the `TEMPS` object.

const fetchTemperatures = (cities, retries = 0) => {
  return fetchTempForCities(cities)
    .then(temps => {

      // Update the `TEMPS` object with updated city temperatures from `temps`
      TEMPS = (TEMPS === null) ? temps : { ...TEMPS, ...temps };

      // Filter the keys (cities) of the `TEMPS` object to get a list of the cities
      // with `null` temperature values.

      const RETRY_CITIES = Object.keys(TEMPS)
        .filter(city => TEMPS[city] == null);

      // If there are 1 or more cities in the `RETRY_CITIES` list
      // and the maximum retries has not been exceeded,
      // attempt to fetch their temperatures again after waiting for 5 seconds.
      // Also increment `retries` by 1.

      if (RETRY_CITIES.length > 0 && retries < MAX_TEMP_FETCH_RETRIES) {
        setTimeout(() => fetchTemperatures(RETRY_CITIES, ++retries), 5 * 1000);
      }

      // Return the updated `TEMPS` object
      return TEMPS;

    })
    .then(console.log, console.error);
}
// Fetch the current temperatures of the cities in the `CITIES` list
// and update the `TEMPS` object

fetchTemperatures(CITIES);

Neste trecho de código, o TEMPSobjeto é usado para manter as temperaturas atualizadas das cidades listadas. A MAX_TEMP_FETCH_RETRIESconstante é um número inteiro que limita o número de tentativas que podem ser feitas para buscas com falha, que é cinco (5) neste caso.

A fetchTemperatures()função recebe um array de nomes de cidades e o número de tentativas até seus argumentos. Ele chama fetchTempForCities()para buscar as temperaturas atuais das cidades passadas para ele e também atualiza o TEMPSobjeto com as temperaturas.

Para buscas com falha, a função agenda outra chamada para si mesma após aguardar cinco segundos e incrementa a contagem de tentativas em 1. As novas tentativas são feitas o maior número de vezes possível, desde que o máximo definido não tenha sido ultrapassado — que é cinco, no nosso caso.

Esperando que tudo seja resolvido

Assim como promise.all()e promise.race()lida com várias promessas, existe outra muito útil, promise.allSettled(), que foi adicionada à especificação JavaScript com o ES2020.

É muito semelhante a promise.all(), mas ao contrário, promise.allSettled()não é rejeitado quando qualquer uma das promessas no iterável passada para ele é rejeitada. Em vez disso, ele espera que todas as promessas sejam resolvidas (cumpridas ou rejeitadas) e, em seguida, retorna um array contendo o resultado de cada promessa. Vejamos um exemplo abaixo.

const promise1 = Promise.resolve("I got fulfilled!");
const promise2 = Promise.reject("I was rejected!");
Promise.allSettled([promise1, promise2]).then((results) =>
  console.log(results)
);

O código acima retornará um resultado como o abaixo:

[
  { status: 'fulfilled', value: 'I got fulfilled!' },
  { status: 'rejected', reason: 'I was rejected!' }
]

Agora, vamos refatorar o trecho de código OpenWeatherAPI que escrevemos acima quando discutimos promise.all(), e implementamos uma possível solução alternativa para casos em que uma das promessas é rejeitada ao capturar os erros.

Com promise.allSettled(), não precisamos dessa solução alternativa. Ele funcionará bem e também veremos a promessa rejeitada juntamente com o motivo. Vamos refatorar o código abaixo:

// Use your OpenWeatherMap API KEY
// Set the current weather data API URL

const API_KEY = "YOUR_API_KEY_HERE";
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;

// Set the list of cities

const CITIES = [
  "Lagos",
  "Berlin",
  "Parwis",  // Tweaked this to cause an error
];

const fetchTempForCity = (city) => {
  return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
    .then((response) => response.json())
    .then((data) => [city, data.main.temp]);
};

const fetchTempForCities = (cities) => {
  return Promise.allSettled(cities.map(fetchTempForCity)).then((temps) => temps);
};

fetchTempForCities(CITIES).then(console.log, console.error);

O resultado deve ser assim:

[
  { status: "fulfilled", value: ["Lagos", "24.18"]},
  { status: "fulfilled", value: ["Berlin", "13.83"]},
  { status: "rejected", 
    reason: TypeError: Cannot read properties of undefined(reading 'temp')
  },
]

Nota: Se um array vazio for passado para promise.settled(), ele retornará uma promessa resolvida com um array vazio como valor.

Corrida de operações assíncronas com promessas

Com as promessas do JavaScript, você pode executar várias operações assíncronas independentes usando o Promise.race()método. Promise.race()aceita um iterável de promessas como seu argumento e retorna uma promessa que é cumprida ou rejeitada da mesma forma que a primeira promessa estabelecida no iterável.

Se a primeira promessa estabelecida no iterável for cumprida com um valor, a promessa de corrida será cumprida com o mesmo valor. No entanto, se for rejeitada, a promessa de corrida será rejeitada pelo mesmo motivo. Se várias promessas forem cumpridas ou rejeitadas ao mesmo tempo, a primeira promessa será usada com base na ordem das promessas no iterável.

Se o iterável passado Promise.race()estiver vazio, a promessa de corrida permanecerá pendente para sempre e nunca será resolvida.

Estudo de caso: resposta de tempo limite

Digamos que você esteja construindo um endpoint de API que faça alguma operação assíncrona, como ler um arquivo ou consultar um banco de dados, e você deseja garantir uma resposta em 5 segundos , caso contrário, a solicitação deve falhar com um código de status HTTP de 504( a resposta de tempo limite do gateway).

O snippet de código a seguir demonstra como Promise.race()pode ser usado para conseguir isso, supondo que estamos construindo a API usando a estrutura Express.js para Node.js.

// Create a new Express app and set the port
const app = require('express')();
const PORT = process.env.PORT || 5000;

// The timeout in seconds for API responses
const TIMEOUT_SECONDS = 5;

// Define a new route on the Express app: GET /random
app.get('/random', (req, res) => {

  /**
   * `execute` is a promise that simulates a time-consuming asynchronous operation
   * which may take anywhere between 1s - 10s to complete its execution.
   * On completion, it is fulfilled with an object that looks like this:
   * {
   *   statusCode: 200,
   *   random: (A random integer in the range of 0 - 100, both inclusive)
   *   duration: (The duration of the execution in seconds, expressed as {duration}s)
   * }
   */

  const execute = new Promise(resolve => {
    // Random execution time in milliseconds
    const timeInMs = Math.floor((Math.random() * 10) * 1000);

    // Simulate execution delay using setTimeout and fulfill the promise
    // with the response object
    setTimeout(() => {
      resolve({
        statusCode: 200,
        random: Math.floor(Math.random() * 101),
        duration: `${timeInMs / 1000}s`
      })
    }, timeInMs);
  });

  /**
   * `requestTimer` is a promise that is settled after `TIMEOUT_SECONDS` seconds
   * On completion, it is fulfilled with an object that looks like this:
   * { statusCode: 504 }
   * which represents a Gateway Timeout on the server.
   */

  const requestTimer = new Promise(resolve => {
    // Simulate execution delay using setTimeout and fulfill the promise
    // with the response object
    const timeoutInMs = TIMEOUT_SECONDS * 1000;
    setTimeout(() => resolve({ statusCode: 504 }), timeoutInMs);
  });

  /**
   * `Promise.race()` is used to run both the `execute` and the `requestTimer` promises.
   * The first of the two promises that gets settled will be used to settle the race promise.
   * The fulfilled response object is then used to form and send the HTTP response.
   * If an error occurs, a HTTP 500 error response is sent.
   */
  return Promise.race([ execute, requestTimer ])
    .then(({ statusCode = 200, ...data }) => {
      const response = res.status(statusCode);

      return (statusCode == 200)
        ? response.json(data)
        : response.end();
    })
    .catch(() => res.status(500).end());

});

// Start the app on the set port

app.listen(PORT, () => console.log(`App is running on port ${PORT}.`));

Neste trecho de código, um aplicativo Express muito minimalista foi configurado com uma única rota —  GET/randompara retornar um inteiro gerado aleatoriamente no intervalo de 0 a 100 (ambos inclusive), ao mesmo tempo em que retorna o tempo de execução.

Promise.race()é usado para esperar a primeira de duas promessas:

  • uma executepromessa que executa alguma operação assíncrona aparentemente demorada e é resolvida após 1s a 10s
  • uma requestTimerpromessa que não faz nada e é resolvida após os TIMEOUT_SECONDSsegundos definidos, que são 5segundos neste caso

Então, aqui está o que acontece: qualquer uma dessas duas promessas que for resolvida primeiro determinará a resposta final do endpoint -   Promise.race()garantirá isso.

Uma técnica semelhante também pode ser usada ao manipular fetcheventos em service workers para detectar redes lentas.

Esperando pela primeira promessa cumprida

Em situações em que queremos devolver a primeira promessa cumprida, promise.any()vem a calhar. Ao contrário promise.race()que retorna a primeira promessa cumprida ou rejeitada, promise.any()retorna a primeira promessa cumprida. Se nenhuma promessa for cumprida, ele retornará uma promessa rejeitada com um AggregateErrorobjeto .

Dos quatro métodos de promessa, (any(), race(), allSettled(), e all(), apenas promise.allSettled()executa todas as promessas passadas a ele porque espera que todas as promessas sejam resolvidas. Os outros não, e por isso dizem que estão em curto-circuito .

Abaixo está uma tabela de um GitHub gist criado por Sung M. Kim mostrando as diferenças entre promise.all(), promise.allSettled(), promise.race()e promise.any().

 

 Curto circuito?Curto-circuito ligado?Cumprido?Rejeitado?
Promessa.tudoSimPrimeira promessa rejeitadaToda promessa cumpridaPrimeira promessa rejeitada
Promise.allSettledNãoN / DSempreN / D
Promessa.corridaSimPrimeiro resolvidoPrimeira promessa cumpridaPrimeira promessa rejeitada
Prometo.qualquerSimPrimeiro preenchidoPrimeira promessa cumpridaTodas as promessas rejeitadas

ver tabela de comparação bruta.md hospedada❤pelo GitHub

 

Usando async/await com promessas

Asynce awaitsão palavras-chave que podem tornar as promessas de escrita mais limpas. Preceder uma função com a palavra-chave asyncfará com que a função retorne uma promessa, que então permite uma palavra- awaitchave dentro dela. Com await, podemos esperar que a promessa seja resolvida. Com asynce await, não precisamos usar .then(). Vamos ver um exemplo abaixo usando o exemplo da API OpenWeather mais uma vez:

// Set the current weather data API URL
const API_KEY = "YOUR_API_KEY";
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;

const fetchTempForCity = async (city) => {
  let response = await fetch(`${API_URL}&q=${encodeURIComponent(city)}`);
  response = await response.json();
  console.log(response)
};

fetchTempForCity('Port harcourt');

Podemos lidar com possíveis erros com o try…catchmétodo. Se a promessa for rejeitada, obteremos o erro no catchbloco.

const fetchTempForCity = async (city) => {
  try {
    let response = await fetch(`${API_URL}&q=${encodeURIComponent(city)}`);
    response = await response.json();
    console.log(response);
  } catch (error) {
    console.error(error.message)
  }
};

fetchTempForCity('Port harcourt');

Conclusão

As promessas do JavaScript podem mudar drasticamente a maneira como você escreve programas assíncronos , tornando seu código mais sucinto e claro em relação à intenção desejada.

Neste guia, vimos várias maneiras pelas quais as promessas podem ser usadas em programas assíncronos, como:

  • Executando operações em sequência, em paralelo e até mesmo competindo-as
  • Como executar várias promessas e esperar que todas sejam resolvidas
  • Como executar promessas a serem encerradas assim que uma for cumprida

Também vimos como usar funções async/await e awaitpromessas de manipulação de palavras-chave. Você pode aprender mais sobre promessas nos documentos de promessa de JavaScript do MDN

Fonte: https://blog.logrocket.com/improve-async-programming-with-javascript-promises/  

#javascrip  #async 

Melhore A Programação Assíncrona Com Promessas De JavaScript

Mejore La Programación Asíncrona Con Promesas De JavaScript

Si ha escrito código JavaScript asíncrono antes, entonces ya tiene una idea sobre el uso de devoluciones de llamada y los problemas con ellas. Un problema importante con el uso de devoluciones de llamada es la posibilidad de encontrarse con un infierno de devolución de llamada.

En ES2015, las promesas de JavaScript se agregaron a la especificación del idioma, lo que provocó un cambio completamente nuevo en la forma en que se escribe el código asincrónico y también mitiga el problema de encontrarse con un infierno de devolución de llamada. Si está utilizando la sintaxis ES2015 en su código, es posible que ya esté familiarizado con las promesas.

En esta guía, aprenderá algunas formas prácticas de mejorar la programación asíncrona en JavaScript usando promesas.

Nota: esta guía no es de ninguna manera una introducción a las promesas de JavaScript. Se requiere algún conocimiento previo de las promesas para leer esta guía.

Creando promesas

Se puede crear una promesa de JavaScript usando el Promiseconstructor. El constructor toma una executorfunción como argumento, que se ejecuta inmediatamente para crear la promesa.

El executor, a su vez, puede tomar dos funciones de devolución de llamada como sus argumentos que se pueden invocar dentro de la función ejecutora para liquidar la promesa, a saber:

  • resolvepor cumplir la promesa con un valor
  • rejectpor rechazar la promesa con una razón (generalmente un error)

Aquí hay una promesa de JavaScript muy simple:

const isLessThan10 = (num) => {
  new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!");
    }
  })
    .then((res) => console.log(res))
    .catch((err) => console.log(err));
};

isLessThan10(14); 

Si ejecuta el código anterior, verá "¡¡Incorrecto!!" en su consola, lo que significa que la promesa se obtuvo rejected. Eso es porque 14obviamente no es menor que 10, pero cuando pasas un número menor que 10, la promesa será fulfilled.

Estados de promesa

En la sección anterior, debe haber notado nuestro uso de dos palabras: rejectedy fulfilled. Estos son dos de los tres estados de una promesa de JavaScript. Hablemos de los tres posibles estados de una promesa.

 

  • rejected– una promesa se rechaza cuando la operación falla, por ejemplo, arriba en la isLessThan10función, cuando pasamos 14, la promesa fue rechazada
  • fulfilled– una promesa se cumple cuando la operación funciona o es correcta, por ejemplo, en la función anterior, pasar un número menor que 10 cumple la promesa
  • Pending– una promesa está pendiente cuando está a la espera de ser resuelta o rechazada. Una promesa solo llega a este estado cuando la operación es asíncrona

Una promesa solo se cumple cuando se resuelve usando un argumento de resolución de promesa. promise.resolvecumple una promesa con un valor, mientras que una promesa se rechaza con el argumento de rechazo de promesa. Estos dos estados muestran que la promesa se liquidó y ya no está pendiente.

promesas saldadas

Muchas veces, solo desea crear una promesa que ya está establecida, ya sea cumplida con un valor o rechazada con una razón. Para casos como este, los métodos Promise.resolve()y Promise.reject()son útiles. Aquí hay un ejemplo simple:

// This promise is already fulfilled with a number (100)
const fulfilledPromise = Promise.resolve(100);

// This promise is already rejected with an error
const rejectedPromise = Promise.reject(new Error('Operation failed.'));

// Getting the rsolved value of the promise
fulfilledPromise.then(res => console.log(res));

// Getting catching to see the error of the promise
rejectedPromise.then(res => console.log(res)).catch(err => console.log(err.message));

También puede haber ocasiones en las que no esté seguro de si un valor es una promesa o no. En casos como este, puede usar Promise.resolve()para crear una promesa cumplida con el valor y luego trabajar con la promesa devuelta. Aquí hay un ejemplo:

// User object
const USER = {
  name: 'Glad Chinda',
  country: 'Nigeria',
  job: 'Fullstack Engineer'
};

// Create a fulfilled promise using Promise.resolve()
Promise.resolve(USER)
  .then(user => console.log(user.name));

Manejo de promesas

Una promesa resuelta se puede manejar pasando devoluciones de llamada a los métodos then(), catch()o finally()de la promesa, como se vio anteriormente en algunos fragmentos de código anteriores. Aquí, refactorizaremos la isLessThan10función y veremos cómo manejar las promesas rechazadas y cumplidas.

const isLessThan10 = (num) => {
  return new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!!");
    }
  })
};

// Case1
isLessThan10(1)
  .then(console.log)
  .catch(console.error);

// Case2
// Alternatively, the rejection can be handled in the same .then() call
// By passing the rejection handler as second argument to .then()
isLessThan10(12).then(console.log, console.error);

Además de usar .catch()para manejar promesas rechazadas, como se vio arriba, también podemos pasar dos devoluciones de llamada a .then(). El primero manejará la promesa si se cumple, mientras que el otro lo hará si se rechaza. También podemos manipular el valor resuelto de la promesa en el then()bloque.

.finally()siempre se ejecuta una vez saldada la promesa, independientemente de que se cumpla o se rechace. Es un buen lugar para realizar acciones de limpieza como restablecer una variable o borrar un estado.

const isLessThan10 = (num) => {
  return new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!!");
    }
  })
    .then(111)
    .catch(222);
};

isLessThan10(11)
  .then((res) => console.log(res))
  .catch((err) => console.error(err))
  .finally(() => console.log("This promise is finally settled!"));

manipuladores tontosthen

El .then()método puede tomar hasta dos funciones de controlador como argumentos: controlador de cumplimiento y controlador de rechazo.

Sin embargo, si alguno de estos dos argumentos no es una función, .then()reemplaza ese argumento con una función y continúa con el flujo de ejecución normal. Se vuelve importante saber con qué tipo de función se reemplaza el argumento. Esto es lo que es:

  • Si el argumento del controlador de cumplimiento no es una función, se reemplaza con una función de identidad. Una función de identidad es una función que simplemente devuelve el argumento que recibe
  • Si el argumento del controlador de rechazo no es una función, se reemplaza con una función lanzadora. Una función lanzadora es una función que simplemente arroja el error o el valor que recibe como argumento.Las funciones de identidad y lanzador escritas

Aquí hay un ejemplo simple:

const isLessThan10 = (num) => {
  return new Promise((resolve, reject) => {
    if (num < 10) {
      resolve("Correct");
    } else {
      reject("Wrong!!!");
    }
  })
    .then(111) // Just a random number 
    .catch(222); // Just a random number
};

//This will log 'Correct' to the console
isLessThan10(3).then(res => console.log(res)).catch(err => console.error(err));

// This will log 'Wrong' to the console
isLessThan10(13).then(res => console.log(res)).catch(err => console.error(err));

Si observa detenidamente, notará que ni la identityfunción ni la throwerfunción alteran el flujo de ejecución normal de la secuencia de promesa. Simplemente tienen el mismo efecto que omitir esa .then()llamada en particular en la cadena de promesa. Por esta razón, normalmente me refiero a estos argumentos de controlador como "controladores tontos".

.then()los manejadores siempre devuelven las promesas

Una cosa importante que debe comprender sobre el .then()método de promesa es que siempre devuelve una promesa.

Aquí hay un desglose de cómo .then()devuelve una promesa en función de lo que devuelve la función del controlador que se le pasó:

 

manejadorFn().después()
nadapromesa que se resuelve conundefined
algún valorpromesa que se resuelve con el valor devuelto
lanza un errorpromesa que se rechaza con el error
promesa ya resueltapromesa que se resuelve al valor de la promesa del controlador devuelto
promesa ya rechazadapromesa que se rechaza con el valor de la promesa del controlador devuelto
otra promesa pendientepromesa cuya resolución/rechazo depende de la resolución/rechazo de la promesa del controlador devuelto

ver raw then-promise.md alojado❤con GitHub

 

Tiempo con promesas

Retrasar la ejecución

Las promesas pueden ser muy útiles para aplicaciones de temporización. Algunos lenguajes de programación como PHP tienen una sleep()función que se puede usar para retrasar la ejecución de una operación hasta después del tiempo de suspensión.

Si bien una sleep()función no existe como parte de la especificación de JavaScript, las funciones globales setTimeout()y setInterval()se usan comúnmente para ejecutar operaciones basadas en el tiempo.

El setInterval()método es una función de JavaScript que se usa para ejecutar un bloque de código en un momento específico con demoras entre cada llamada, mientras que el setTimeout()método se usa para agregar un temporizador a un bloque de código de JavaScript.

Así es como sleep()se puede simular la función usando promesas en JavaScript. Sin embargo, en esta versión de la sleep()función, el tiempo de parada será en milisegundos en lugar de segundos :

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

Aquí hay una versión ligeramente ampliada y anotada de la sleep(ms)función:

const sleep = ms => {
  // Return a new promise
  // No need defining the executor function with a `reject` callback
  return new Promise(resolve => {
    // Pass resolve as the callback to setTimeout
    // This will execute `resolve()` after `ms` milliseconds
    setTimeout(resolve, ms);
  });
}

La sleep(ms)función puede incluso mejorarse aún más para convertirse en una delayfunción autónoma que ejecuta una callbackfunción después del tiempo de suspensión especificado.

Así es sleep()como se vería el uso de la función:

// Sleep for 5 seconds
// Then execute the operation
sleep(5000).then(executeOperation);

// Delay function
// Using async/await with sleep()
const delay = async (callback, seconds = 1) => {
  // Sleep for the specified seconds
  // Then execute the operation
  await sleep(seconds * 1000);
  callback();
}

// Using the `delay()` function
// Execution delayed by 5 seconds
delay(executeOperation, 5);

Medición del tiempo de ejecución

¿Qué sucede si está interesado en saber cuánto tiempo tardó en completarse una operación asíncrona? Este suele ser el caso cuando se compara el rendimiento de alguna forma de implementación o funcionalidad.

Aquí hay una implementación simple que aprovecha una promesa de JavaScript para calcular el tiempo de ejecución de una operación asincrónica.

const timing = callback => {
  // Get the start time using performance.now()
  const start = performance.now();

  // Perform the asynchronous operation
  // Finally, log the time difference
  return Promise.resolve(callback())
    .finally(() => console.log(`Timing: ${performance.now() - start}`));
}

En esta implementación, performance.now()se usa en lugar de Date.now()para obtener la marca de tiempo con una resolución más alta. Para entornos sin navegador donde el performanceobjeto no existe, puede recurrir al uso Date.now()u otras implementaciones de host.

En el bloque de código a continuación, la timing()función podría usarse para registrar el tiempo de ejecución de una operación asíncrona en la consola:

// Async operation that takes between 1 - 5 seconds
const asyncOperation = () => new Promise(resolve => {
  setTimeout(() => resolve('DONE'), Math.ceil(Math.random() * 5) * 1000);
});

// Compute execution time in ms
// And log it to the console
timing(asyncOperation); // Timing: 4003.4000000014203

Ejecución secuencial con promesas

Con las promesas de JavaScript, puede ejecutar operaciones asincrónicas en secuencia . Este suele ser el caso cuando una operación asíncrona posterior depende de la ejecución de una operación asíncrona anterior, o cuando se requiere el resultado de una operación asíncrona anterior para una operación posterior.

La ejecución de operaciones asincrónicas en secuencia generalmente implica encadenar uno o más .``then()controladores .catch()a una promesa. Cuando se rechaza una promesa en la cadena, la maneja el controlador de rechazo definido en el siguiente .then()controlador de la cadena y luego la ejecución continúa en la cadena.

Sin embargo, si no se ha definido un controlador de rechazo en el siguiente .then()controlador de la cadena, el rechazo de la promesa cae en cascada por la cadena hasta que llega al primer .catch()controlador.

Estudio de caso: aplicación de galería de fotos

Supongamos que está creando una aplicación de galería de fotos y desea poder obtener fotos de un repositorio de fotos en línea, luego filtrarlas por formato, relación de aspecto, rangos de dimensiones, etc.

Aquí hay algunas funciones posibles que podría tener en su aplicación:

/**
 * Fetches photos from the Picsum API
 * @returns {Promise} A promise that is fulfilled with an array of photos from the Picsum repository
 */

const fetchPhotos = () =>
  fetch('https://picsum.photos/list')
    .then(response => response.json());

/**
 * Filters photos and returns only JPEG photos 
 * @param {Array} photos
 * @returns {Array} An array of JPEG photos
 */
const jpegOnly = photos =>
  photos.filter(({ format }) => format.toLowerCase() === 'jpeg')

/**
 * Filters photos and returns only square photos
 * @param {Array} photos
 * @returns {Array} An array of square photos
 */

const squareOnly = photos =>
  photos.filter(({ width, height }) => height && Number.isFinite(height) && (width / height) === 1)

/**
 * Returns a function for filtering photos by size based on `px`
 * @param {number} px The maximum allowed photo dimension in pixels
 * @returns {Function} Function that filters photos and returns an array of photos smaller than `px`
 */

const smallerThan = px => photos =>
  photos.filter(({ width, height }) => Math.max(width, height) < px)

/**
 * Return an object containing the photos count and URLs.
 * @param {Array} photos
 * @returns {Object} An object containing the photos count and URLs
 */

const listPhotos = photos => ({
  count: photos.length,
  photos: photos.map(({ post_url }) => post_url)
})

En el bloque de código anterior, la fetchPhotos()función obtiene una colección de fotos de Picsum Photos API utilizando la función global fetch()proporcionada por Fetch API y devuelve una promesa que se cumple con una colección de fotos.

Así es como se ve la colección devuelta por la API de Picsum Photos :

La colección devuelta por la API de Picsum Photos

Las funciones de filtro aceptan una colección de fotos como argumento y filtran la colección de alguna de las siguientes maneras:

  • jpegOnly()  — filtra una colección de fotos y devuelve una subcolección de solo imágenes JPEG
  • squareOnly()  — filtra una colección de fotos y devuelve una subcolección de solo fotos con una relación de aspecto cuadrada
  • smallerThan()  — esta es una función de orden superior que toma una dimensión y devuelve una función de filtro de fotos que devuelve una subcolección de fotos cuyas dimensiones máximas son más pequeñas que el umbral de dimensión especificado

Digamos que queremos ejecutar esta secuencia de operaciones:

  1. Obtener la colección de fotos
  2. Filtra la colección dejando solo fotos JPEG
  3. Filtre la colección dejando solo fotos con una relación de aspecto cuadrada
  4. Filtre la colección dejando solo fotos de menos de 2500px
  5. Extraiga el recuento de fotos y las URL de la colección
  6. Registre la salida final en la consola
  7. Registrar error en la consola si se produjo un error en cualquier punto de la secuencia

El siguiente fragmento de código muestra cómo podemos encadenar la ejecución de estas operaciones en una secuencia de promesa:

// Execute asynchronous operations in sequence
fetchPhotos()
  .then(jpegOnly)
  .then(squareOnly)
  .then(smallerThan(2500))
  .then(listPhotos)
  .then(console.log)
  .catch(console.error);

El código anterior generará un resultado similar a la imagen a continuación:

La salida de nuestra secuencia de operaciones asíncronas

Ejecutar y ejecutar promesas de JavaScript en paralelo

Con las promesas de JavaScript, puede ejecutar múltiples operaciones asincrónicas independientes en lotes o en paralelo usando el Promise.all()método.

Promise.all()acepta un iterable de promesas como argumento y devuelve una promesa que se cumple cuando se cumplen todas las promesas del iterable, o se rechaza cuando se rechaza una de las promesas del iterable.

Si la promesa devuelta se cumple, se cumple con una matriz de todos los valores de las promesas cumplidas en el iterable (en el mismo orden). Sin embargo, si rechaza, se rechaza debido a la primera promesa en el iterable que rechazó.

Estudio de caso: Temperaturas actuales

Supongamos que está creando una aplicación meteorológica que permite a los usuarios ver las temperaturas actuales de una lista de ciudades que han seleccionado.

Con Promise.all(), puede realizar una GETsolicitud a la API meteorológica para obtener la temperatura de todas las ciudades seleccionadas a la vez, de modo que sus usuarios no vean la representación de datos una tras otra en su aplicación.

El siguiente fragmento de código demuestra cómo obtener las temperaturas actuales de las ciudades seleccionadas en paralelo con Promise.all().

El servicio API de OpenWeatherMap se utilizará para obtener los datos meteorológicos, por lo que si desea seguir, diríjase a su sitio web siguiendo ese enlace y regístrese para obtener una clave API.

// Use your OpenWeatherMap API KEY
// Set the current weather data API URL
const API_KEY = 'YOUR_API_KEY_HERE';
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;

// Set the list of cities
const CITIES = [
  'London', 'Tokyo', 'Melbourne', 'Vancouver',
  'Lagos', 'Berlin', 'Paris', 'Johannesburg',
  'Chicago', 'Mumbai', 'Cairo', 'Beijing'
];

const fetchTempForCity = city => {
  return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
    .then(response => response.json())
    .then(data => [ city, data.main.temp || null ]);
}

const fetchTempForCities = cities => {
  return Promise.all(cities.map(fetchTempForCity))
    .then(temps => {
      return temps.reduce((data, [ city, temp ]) => {
        return { ...data, [city]: Number.isFinite(temp) ? temp.toFixed(2) * 1 : null };
      }, {});
    });
}

fetchTempForCities(CITIES)
  .then(console.log, console.error);

En el bloque de código anterior, creamos las siguientes funciones:

  • fetchTempForCity() — acepta una sola ciudad como argumento y devuelve una promesa que se cumple con la temperatura actual de la ciudad dada (en °C) llamando al servicio API de OpenWeatherMap. La promesa devuelta se cumple con una matriz del formato:[city, temperature]
  • fetchTempForCities()— acepta una serie de ciudades y obtiene la temperatura actual de cada ciudad aprovechando Array.prototype.map()para llamar a la fetchTempForCity()función en cada ciudad.

El Promise.all()método se usa para ejecutar las solicitudes en paralelo y acumular sus datos en una sola matriz que, a su vez, se reduce a un objeto mediante una Array.prototype.reduce()función .

El fragmento de código anterior devolverá un objeto similar al siguiente resultado:

Resultados del fragmento de código de la API de OpenWeather

Manejo de rechazo

Es importante tener en cuenta que si alguna de las promesas de obtención de temperatura pasadas Promise.all()se rechaza por un motivo, todo el lote de promesas se rechazará inmediatamente por el mismo motivo.

Es decir, si por alguna razón se rechaza al menos una de las doce promesas de obtención de temperatura, se rechazará todo el lote de la promesa y, por lo tanto, no se devolverá la temperatura de la promesa.

El escenario descrito anteriormente no suele ser el comportamiento deseado en la mayoría de los casos: una búsqueda de temperatura fallida no debería hacer que se descarten los resultados de las búsquedas exitosas en el lote. Podemos arreglar esto fácilmente usando otro método de promesa promise.allSettled(), del que hablaremos más adelante, pero también hay otra solución sencilla.

La solución sencilla para esto es adjuntar un .catch()controlador a la fetchTempForCitypromesa, lo que hace que cumpla la promesa con un valor de temperatura nulo en casos de rechazo.

Así es como se verá:

const fetchTempForCity = city => {
  return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
    .then(response => response.json())
    .then(data => [ city, data.main.temp || null ])

    // Attach a `.catch()` handler for graceful rejection handling
    .catch(() => [ city, null ]);
}

Con ese pequeño cambio en la fetchTempForCity()función, ahora hay una gran garantía de que la promesa devuelta nunca será rechazada en los casos en que la solicitud falle o algo salga mal. Más bien, se cumplirá con una matriz del formato: [city, null], como la siguiente:

La ciudad, formato nulo
Con este cambio, es posible mejorar aún más el código para poder programar reintentos para recuperaciones de temperatura fallidas.

El siguiente fragmento de código incluye algunas adiciones que se pueden hacer al código anterior para que esto sea posible.

// An object that will contain the current temperatures of the cities

// The keys are the city names, while the values are their current temperatures (in °C)

let TEMPS = null;

// The maximum number of retries for failed temperature fetches

const MAX_TEMP_FETCH_RETRIES = 5;

// Fetches the current temperatures of multiple cities (in °C) and update the `TEMPS` object.

const fetchTemperatures = (cities, retries = 0) => {
  return fetchTempForCities(cities)
    .then(temps => {

      // Update the `TEMPS` object with updated city temperatures from `temps`
      TEMPS = (TEMPS === null) ? temps : { ...TEMPS, ...temps };

      // Filter the keys (cities) of the `TEMPS` object to get a list of the cities
      // with `null` temperature values.

      const RETRY_CITIES = Object.keys(TEMPS)
        .filter(city => TEMPS[city] == null);

      // If there are 1 or more cities in the `RETRY_CITIES` list
      // and the maximum retries has not been exceeded,
      // attempt to fetch their temperatures again after waiting for 5 seconds.
      // Also increment `retries` by 1.

      if (RETRY_CITIES.length > 0 && retries < MAX_TEMP_FETCH_RETRIES) {
        setTimeout(() => fetchTemperatures(RETRY_CITIES, ++retries), 5 * 1000);
      }

      // Return the updated `TEMPS` object
      return TEMPS;

    })
    .then(console.log, console.error);
}
// Fetch the current temperatures of the cities in the `CITIES` list
// and update the `TEMPS` object

fetchTemperatures(CITIES);

En este fragmento de código, el TEMPSobjeto se usa para contener las temperaturas actualizadas de las ciudades enumeradas. La MAX_TEMP_FETCH_RETRIESconstante es un número entero que limita el número de reintentos que se pueden realizar para recuperaciones fallidas, que es cinco (5) en este caso.

La fetchTemperatures()función recibe una matriz de nombres de ciudades y el número de reintentos hasta sus argumentos. Llama fetchTempForCities()para obtener las temperaturas actuales de las ciudades que se le pasan, y también actualiza el TEMPSobjeto con las temperaturas.

Para recuperaciones fallidas, la función programa otra llamada a sí misma después de esperar cinco segundos e incrementa el conteo de reintentos en 1. Los reintentos se realizan tantas veces como sea posible, siempre que no se haya superado el máximo establecido, que en nuestro caso son cinco.

Esperando a que todo se resuelva

Así como promise.all()y promise.race()manejar múltiples promesas, hay otra muy útil promise.allSettled(), que se agregó a la especificación de JavaScript con ES2020.

Es muy similar a promise.all(), pero a diferencia de él, promise.allSettled()no se rechaza cuando se rechaza cualquiera de las promesas en el iterable que se le pasa. En su lugar, espera a que se liquiden todas las promesas (cumplidas o rechazadas) y luego devuelve una matriz que contiene el resultado de cada promesa. Veamos un ejemplo a continuación.

const promise1 = Promise.resolve("I got fulfilled!");
const promise2 = Promise.reject("I was rejected!");
Promise.allSettled([promise1, promise2]).then((results) =>
  console.log(results)
);

El código anterior devolverá un resultado como el siguiente:

[
  { status: 'fulfilled', value: 'I got fulfilled!' },
  { status: 'rejected', reason: 'I was rejected!' }
]

Ahora, refactoricemos el fragmento de código OpenWeatherAPI que escribimos anteriormente cuando discutimos promise.all()e implementamos una posible solución para los casos en los que se rechaza una de las promesas al detectar los errores.

Con promise.allSettled(), no necesitamos esa solución. Funcionará bien y también veremos la promesa rechazada junto con el motivo. Refactoricemos el siguiente código:

// Use your OpenWeatherMap API KEY
// Set the current weather data API URL

const API_KEY = "YOUR_API_KEY_HERE";
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;

// Set the list of cities

const CITIES = [
  "Lagos",
  "Berlin",
  "Parwis",  // Tweaked this to cause an error
];

const fetchTempForCity = (city) => {
  return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
    .then((response) => response.json())
    .then((data) => [city, data.main.temp]);
};

const fetchTempForCities = (cities) => {
  return Promise.allSettled(cities.map(fetchTempForCity)).then((temps) => temps);
};

fetchTempForCities(CITIES).then(console.log, console.error);

El resultado debería ser así:

[
  { status: "fulfilled", value: ["Lagos", "24.18"]},
  { status: "fulfilled", value: ["Berlin", "13.83"]},
  { status: "rejected", 
    reason: TypeError: Cannot read properties of undefined(reading 'temp')
  },
]

Nota: si se pasa una matriz vacía a promise.settled(), devolverá una promesa resuelta con una matriz vacía como valor.

Carreras de operaciones asíncronas con promesas

Con las promesas de JavaScript, puede ejecutar varias operaciones asincrónicas independientes utilizando el Promise.race()método. Promise.race()acepta un iterable de promesas como argumento y devuelve una promesa que se cumple o rechaza de la misma manera que la primera promesa establecida en el iterable.

Si la primera promesa asentada en el iterable se cumple con un valor, la promesa de carrera se cumple con el mismo valor. Sin embargo, si es rechazada, la promesa de carrera será rechazada por la misma razón. Si se cumplen o rechazan varias promesas al mismo tiempo, se usará la primera promesa según el orden de las promesas en el iterable.

Si el iterable al que se pasa Promise.race()está vacío, la promesa de carrera permanece pendiente para siempre y nunca se liquida.

Estudio de caso: respuesta de tiempo de espera

Supongamos que está creando un punto final de API que realiza alguna operación asíncrona, como leer de un archivo o consultar una base de datos, y desea garantizar que obtendrá una respuesta en 5 segundos; de lo contrario, la solicitud debería fallar con un código de estado HTTP de 504( la respuesta de tiempo de espera de puerta de enlace).

El siguiente fragmento de código demuestra cómo Promise.race()se puede usar para lograr esto, suponiendo que estamos construyendo la API usando el marco Express.js para Node.js.

// Create a new Express app and set the port
const app = require('express')();
const PORT = process.env.PORT || 5000;

// The timeout in seconds for API responses
const TIMEOUT_SECONDS = 5;

// Define a new route on the Express app: GET /random
app.get('/random', (req, res) => {

  /**
   * `execute` is a promise that simulates a time-consuming asynchronous operation
   * which may take anywhere between 1s - 10s to complete its execution.
   * On completion, it is fulfilled with an object that looks like this:
   * {
   *   statusCode: 200,
   *   random: (A random integer in the range of 0 - 100, both inclusive)
   *   duration: (The duration of the execution in seconds, expressed as {duration}s)
   * }
   */

  const execute = new Promise(resolve => {
    // Random execution time in milliseconds
    const timeInMs = Math.floor((Math.random() * 10) * 1000);

    // Simulate execution delay using setTimeout and fulfill the promise
    // with the response object
    setTimeout(() => {
      resolve({
        statusCode: 200,
        random: Math.floor(Math.random() * 101),
        duration: `${timeInMs / 1000}s`
      })
    }, timeInMs);
  });

  /**
   * `requestTimer` is a promise that is settled after `TIMEOUT_SECONDS` seconds
   * On completion, it is fulfilled with an object that looks like this:
   * { statusCode: 504 }
   * which represents a Gateway Timeout on the server.
   */

  const requestTimer = new Promise(resolve => {
    // Simulate execution delay using setTimeout and fulfill the promise
    // with the response object
    const timeoutInMs = TIMEOUT_SECONDS * 1000;
    setTimeout(() => resolve({ statusCode: 504 }), timeoutInMs);
  });

  /**
   * `Promise.race()` is used to run both the `execute` and the `requestTimer` promises.
   * The first of the two promises that gets settled will be used to settle the race promise.
   * The fulfilled response object is then used to form and send the HTTP response.
   * If an error occurs, a HTTP 500 error response is sent.
   */
  return Promise.race([ execute, requestTimer ])
    .then(({ statusCode = 200, ...data }) => {
      const response = res.status(statusCode);

      return (statusCode == 200)
        ? response.json(data)
        : response.end();
    })
    .catch(() => res.status(500).end());

});

// Start the app on the set port

app.listen(PORT, () => console.log(`App is running on port ${PORT}.`));

En este fragmento de código, se ha configurado una aplicación Express muy minimalista con una sola ruta:  GET/randompara devolver un número entero generado aleatoriamente en el rango de 0 a 100 (ambos inclusive), al mismo tiempo que devuelve el tiempo de ejecución.

Promise.race()se usa para esperar la primera de dos promesas:

  • una executepromesa que realiza una operación asíncrona que aparentemente consume mucho tiempo y se liquida después de 1 s - 10 s
  • una requestTimerpromesa que no hace nada y se liquida después de los TIMEOUT_SECONDSsegundos establecidos, que son 5segundos en este caso

Entonces, esto es lo que sucede: cualquiera de estas dos promesas que se resuelva primero determinará la respuesta final desde el punto final,   Promise.race()se asegurará de eso.

También se puede usar una técnica similar cuando se manejan fetcheventos en trabajadores de servicio para detectar redes lentas.

Esperando la primera promesa cumplida

En situaciones en las que queremos devolver la primera promesa cumplida, promise.any()viene muy bien. A diferencia promise.race()de que devuelve la primera promesa cumplida o rechazada, promise.any()devuelve la primera promesa cumplida. Si no se cumple ninguna promesa, devolverá una promesa rechazada con un AggregateErrorobjeto .

De los cuatro métodos de promesa, (any(), race(), allSettled(), y all(), solo promise.allSettled()ejecuta todas las promesas que se le pasan porque espera que se resuelvan todas las promesas. Los otros no, por lo que se dice que tienen un cortocircuito .

A continuación se muestra una tabla de una esencia de GitHub creada por Sung M. Kim que muestra las diferencias entre promise.all(), promise.allSettled(), promise.race()y promise.any().

 

 ¿Cortocircuito?¿Cortocircuitos activados?Cumplido el?¿Rechazado?
Promise.allPrimera promesa rechazadaToda promesa cumplidaPrimera promesa rechazada
Promise.allSettledNoN / ASiempreN / A
Promesa.carreraPrimero liquidadoPrimera promesa cumplidaPrimera promesa rechazada
Promesa.cualquieraPrimero cumplidoPrimera promesa cumplidaTodas las promesas rechazadas

ver Raw Comparison Table.md alojado❤con GitHub

 

Usando async/await con promesas

Asyncy awaitson palabras clave que pueden hacer que escribir promesas sea más limpio. Anteceder una función con la palabra clave asynchará que la función devuelva una promesa, que luego le permite una awaitpalabra clave dentro. Con await, podemos esperar a que se resuelva la promesa. Con asyncy await, no necesitamos usar .then(). Veamos un ejemplo a continuación usando el ejemplo de la API de OpenWeather una vez más:

// Set the current weather data API URL
const API_KEY = "YOUR_API_KEY";
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;

const fetchTempForCity = async (city) => {
  let response = await fetch(`${API_URL}&q=${encodeURIComponent(city)}`);
  response = await response.json();
  console.log(response)
};

fetchTempForCity('Port harcourt');

Podemos manejar posibles errores con el try…catchmétodo. Si se rechaza la promesa, obtendremos el error en el catchbloque.

const fetchTempForCity = async (city) => {
  try {
    let response = await fetch(`${API_URL}&q=${encodeURIComponent(city)}`);
    response = await response.json();
    console.log(response);
  } catch (error) {
    console.error(error.message)
  }
};

fetchTempForCity('Port harcourt');

Conclusión

Las promesas de JavaScript pueden cambiar drásticamente la forma en que escribe programas asincrónicos , haciendo que su código sea más breve y claro con respecto a la intención deseada.

En esta guía, analizamos varias formas en que se pueden usar las promesas en programas asincrónicos, como:

  • Ejecutar operaciones en secuencia, en paralelo e incluso correrlas
  • Cómo ejecutar múltiples promesas y esperar a que todas se resuelvan
  • Cómo ejecutar promesas para ser rescindidas tan pronto como se cumpla una

También vimos cómo usar las funciones async/await y las awaitpromesas de manejo de palabras clave. Puede obtener más información sobre las promesas en los documentos de promesas de JavaScript de MDN

Fuente: https://blog.logrocket.com/improve-async-programming-with-javascript-promises/ 

#javascrip  #async 

Mejore La Programación Asíncrona Con Promesas De JavaScript
藤本  結衣

藤本 結衣

1649266620

宣言型JavaScriptプロミスラッパーの書き方

JavaScriptはシングルスレッドのプログラミング言語です。つまり、JavaScriptは、コードを同期的に実行することも、一度に1行上から下に実行することしかできません。ただし、この問題に対処するために非同期プログラミングが導入されました。

このコアJavaScriptの概念により、他の関数の実行が終了するのを待っている間に関数を実行できます。非同期関数を使用して、バックエンドへのAPI呼び出しを行います。また、ファイルやデータベースへの書き込みと読み取りにも使用します。この概念は、サーバー側の開発者とクライアント側の開発者の両方に役立ちます。

このガイドでは、JavaScriptで宣言型の非同期関数呼び出しを作成する方法を示します。また、コードを読みやすく、保守しやすくするためにどのように役立つかについても説明します。

宣言型プログラミング

コードに飛び込む前に、宣言型プログラミングパターンを確認しましょう。

宣言型プログラミングはプログラミングパラダイムであり、一般にコードのロジックを示しますが、そこに到達するための手順は示しません。このタイプのプログラミングでは、舞台裏で何が起こっているのかは一般的に明らかではありません。

逆に、命令型プログラミングでは、ステップバイステップのコードを記述し、各ステップを詳細に説明する必要があります。これは、コードを操作する必要がある将来の開発者に役立つ背景を提供できますが、コードが非常に長くなります。多くの場合、命令型プログラミングは不要です。それは私たちの目的に依存します。

宣言型プログラミングは、組み込みのJavaScriptメソッドを使用して実現できます。宣言型プログラミングを使用すると、より読みやすく、したがって理解しやすいコードを記述できます。

たとえば、宣言型プログラミングではfor、配列を反復処理するためにループを使用する必要はありません。代わりに、、、、などmap()の組み込みの配列メソッドを使用できます。reduce()forEach()

これは命令型プログラミングの例であり、デクリメントforループを使用して文字列を反転する関数を示しています。

const reverseString = (str) => {
    let reversedString = "";

    for (var i = str.length - 1; i >= 0; i--) { 
        reversedString += str[i];
    }
    return reversedString; 
}

しかし、たった2行のコードで同じソリューションを実現できるのに、なぜ10行のコードを書くのでしょうか。

JavaScriptに組み込まれた配列メソッドを使用した、同じコードの宣言型プログラミングバージョンを次に示します。

const reverseString = (str) => {
  return str.split("").reverse().join("");  
} 

このコードスニペットは、2行のコードを使用して文字列を反転します。それは非常に短く、要点にまっすぐになります。

promise

promiseは、非同期関数の結果を含むJavaScriptオブジェクトです。つまり、非同期関数で完了または失敗したタスクを表します。

const promise = new Promise (function (resolve, reject) {
    // code to execute
})

promiseコンストラクターは1つの引数を取ります。これは、エグゼキューターとも呼ばれるコールバック関数です。エグゼキュータ関数は、2つのコールバック関数を取ります:resolvereject。エグゼキュータ関数が正常に実行されると、resolve()メソッドが呼び出され、promise状態が保留から実行済みに変わります。エグゼキュータ関数が失敗すると、reject()メソッドが呼び出され、promise状態が保留から失敗に変わります。

解決された値にアクセスするには、このメソッドを使用して、以下に示すよう.then ()にチェーンします。promise

promise.then(resolvedData => {
  // do something with the resolved value
})

同様に、拒否された値の場合、次の.catch()方法が使用されます。

promise.then(resolvedData => {
  // do something with the resolved value
}).catch(err => {
  // handle the rejected value
})

async-await

ネストされたコールバックまたは.then関数が複数ある場合、コードとその可読性を維持することが困難になることがよくあります。

このasyncキーワードは、JavaScriptで非同期操作を処理する関数を定義するのに役立ちます。一方、awaitキーワードは、結果を返す前に関数が完了するのを待つようにJavaScriptエンジンに指示するために使用されます。

構文は、async-awaitpromiseを取り巻く構文上の糖衣です。これは、保守が容易な、よりクリーンなコードを実現するのに役立ちます。

const getUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await res.json();
  return data;
}

async-awaitpromiseまたは非同期関数を同期的に実行できるようにします。ただし、予期しないエラーを回避するためawaitに、キーワードをブロックでラップすることをお勧めします。try-catch

await次のように、キーワードとgetUsers()関数をtry-catchブロックでラップする例を次に示します。

const onLoad = async () => {
  try {
    const users = await getUsers();
    // do something with the users
  } catch (err) {
    console.log(err)
    // handle the error
  }
}

カスタムpromiseラッパー

最新のJavaScriptのこのような素晴らしい機能の1つは、async-awaitコールバックの地獄を回避するのに役立つことです。

asyncそれでも、複数の関数のエラーを処理すると、次のような結果になる可能性があります。

try {
  const a = await asyncFuncOne();
} catch (errA) {
  // handle error
}

try {
  const b = await asyncFunctionTwo();
} catch (errB) {
  // handle error
}

try {
  const c = await asyncFunctionThree();
} catch (errC) {
  // handle error
}

async1つのブロックにすべての関数を追加すると、ブロックがより一般的になるため、ブロックに複数の条件をtry書き込むことになります。ifcatchcatch

try {
  const a = await asyncFuncOne();
  const b = await asyncFunctionTwo();
  const c = await asyncFunctionThree();
} catch (err) {
  if(err.message.includes('A')) {
    // handle error for asyncFuncOne
  }
  if(err.message.includes('B')) {
    // handle error for asyncFunctionTwo
  }
  if(err.message.includes('C')) {
    // handle error for asyncFunctionThree
  }
}

これにより、構文を使用しても、コードが読みにくくなり、保守が困難になりasync-awaitます。

この問題を解決するために、をラップして繰り返しブロックpromiseを回避するユーティリティ関数を作成できます。try-catch

ユーティリティ関数は、パラメータとしてaを受け入れ、promise内部でエラーを処理し、解決された値と拒否された値の2つの要素を持つ配列を返します。

この関数はを解決しpromise、配列の最初の要素のデータを返します。エラーは配列の2番目の要素に返されます。約束が解決された場合、2番目の要素はとして返されnullます。

const promiser = async (promise) => {
  try {
    const data = await promise;
    return [data, null]
  } catch (err){
    return [null, error]
  }
}

上記のコードをさらにリファクタリングし、 usingメソッドとhandlerメソッドをtry-catch返すだけでブロックを削除できます。promise.then().catch()

const promiser = (promise) => {
  return promise.then((data) => [data, null]).catch((error) => [null, error]);
};

以下にユーティリティの使用法を示します。

const demoPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("Yaa!!");
    reject("Naahh!!");
  }, 5000);
});

const runApp = async () => {
  const [data, error] = await promiser(demoPromise);
  if (error) {
    console.log(error);
    return;
  }
  // do something with the data
};

runApp();

それでは、実際のユースケースを見てみましょう。以下では、このgenerateShortLink関数はURL短縮サービスを使用して完全長のURLを短縮します。

ここでは、メソッドはURL短縮サービスからの応答を返す関数axios.get()によってラップされています。promiser()

import promiser from "./promise-wrapper";
import axios from "axios";

const generateShortLink = async (longUrl) => {
  const [response, error] = await promiser(
    axios.get(`https://api.1pt.co/addURL?long=${longUrl}`)
  );

  if (error) return null;

  return `https://1pt.co/${response.data.short}`;
};

promiser()比較のために、ラッパー関数がないと関数は次のようになります。

const generateShortLink = async (longUrl) => {
  try {
    const response = await axios.get(
      `https://api.1pt.co/addURL?long=${longUrl}`
    );
    return `https://1pt.co/${response.data.short}`;
  } catch (err) {
    return null;
  }
};

generateShortLink()次に、次のメソッドを使用するフォームを作成して、例を完成させましょう。

const form = document.getElementById("shortLinkGenerator");

const longUrlField = document.getElementById("longUrl");

const result = document.getElementById("result");

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const longUrl = longUrlField.value;
  const shortLink = await generateShortLink(longUrl);
  if (!shortLink) result.innerText = "Could not generate short link";
  else result.innerHTML = `<a href="${shortLink}">${shortLink}</a>`;
});


<!-- HTML -->
<!DOCTYPE html>
<html>
  <head>
    <title>Demo</title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div id="app">
      <form id="shortLinkGenerator">
        <input type="url" id="longUrl" />
        <button>Generate Short Link</button>
      </form>
      <div id="result"></div>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

参考までに、完全なコードとデモを次に示します。

これまでのところ、promiser()関数は1つの関数のみをラップできasyncます。ただし、ほとんどのユースケースでは、複数の独立した機能を処理する必要がありasyncます。

多くのpromiseを処理するために、Promise.all()メソッドを使用して関数の配列をasync関数に渡すことができますpromiser

const promiser = (promise) => {
  if (Array.isArray(promise)) promise = Promise.all(promise);
  return promise.then((data) => [data, null]).catch((error) => [null, error]);
};

promiser()複数の関数で使用される関数の例を次に示しasyncます。

import axios from "axios";
import promiser from "./promiser";

const categories = ["science", "sports", "entertainment"];

const requests = categories.map((category) =>
  axios.get(`https://inshortsapi.vercel.app/news?category=${category}`)
);

const runApp = async () => {
  const [data, error] = await promiser(requests);
  if (error) {
    console.error(error?.response?.data);
    return;
  }
  console.log(data);
};

runApp();

結論

JavaScriptで宣言型非同期関数呼び出しを作成するためにこのガイドで共有されているソリューションは、ほとんどのシナリオに最適です。ただし、考慮する必要のある追加のユースケースがあります。たとえば、予想されるエラーのみを処理し、promise実行中に発生する例外的なエラーをスローしたい場合があります。

どのアプローチにもトレードオフがあります。特定のユースケースでそれらを理解し、考慮に入れることが重要です。

この記事で共有されている知識は、コーディングの旅を続けるときに、より複雑なAPIとユーティリティ関数を作成するための優れたエントリポイントです。

幸運と幸せなコーディング!

ソース:https ://blog.logrocket.com/write-declarative-javascript-promise-wrapper/

#javascrip 

宣言型JavaScriptプロミスラッパーの書き方
Mary  Turcotte

Mary Turcotte

1648785600

How to Build A Fullscreen Slider with Custom Navigation With CSS/JS

In this video I will show you how to build a Fullscreen slider with custom navigation with CSS/JavaScript

You can see more at: https://morioh.com/p/c52eff6c6306

#css #javascrip 

How to Build A Fullscreen Slider with Custom Navigation With CSS/JS

learn about Operators in javascript In 10 Minutes

I repeat some of the things in this lesson it means they are important.

Beginners course on javascript. If you want to become a modern day developer javascript is the language you must learn. Join me on this course you will learn everything you need to learn about JavaScript

This lesson covers Operators in javascript in this lesson I don't cover every operator but these are the most commonly used  :)

#javascrip 

learn about Operators in javascript In 10 Minutes

Tailwind CSS As A Templating Language in JS and CSS-in-JS

This repo contains a collection of packages that makes the integration of Tailwind with CSS-in-JS libraries easier.

Why does this exist?

You may have encountered some of these problems when using Tailwind with CSS-in-JS libraries.

  • You have to use PurgeCSS to get the minimal CSS file, PurgeCSS relies on string matching
  • No warnings when misspelling, refactoring or using a class that doesn't exist
  • Inline classes can get very long and hard to read
  • You have to specify the variants for utility classes in tailwind.config.js

Features / Goals

  • Solve all of the above problems
  • Automatically compatible with latest Tailwind version 2.X.X
  • New syntax to apply variants to multiple utility classes md:hover[text-xs font-normal]
  • Reacts to changes in made in tailwind.config.js
  • Great developer experience with VS Code extension or typescript-xwind-plugin
  • No runtime impact all transformations happen during build time
  • Plugins to support any/your favorite CSS-in-JS syntax

Support for all Tailwind features:

  • All utility and component classes
  • All variant utility classes enabled
  • Full support for custom classes and tailwind.config.js customization
  • Supports Tailwind plugins (@tailwindcss/typography, @tailwindcss/forms, ...)

Packages

xwind

xwind uses a babel plugin that transforms Tailwind classes into CSS object styles or a classes string. The CSS object styles output can be used with your favorite CSS-in-JS library like emotion, styled-components ... The classes string output can be used with the xwind cli to generate a minimal css file of the used Tailwind classes.

Output mode "objectstyles" example

import xw from "xwind";

const styles = xw`text-red-100 hover:text-green-100 hover:bg-blue-200`;
// OR (with custom array syntax)
const styles = xw`text-red-100 hover[text-green-100 bg-blue-200]`;

Transforms by default into Postcss-js / JSS compatible syntax:

const styles = {
  "--text-opacity": "1",
  color: ["#fde8e8", "rgba(253, 232, 232, var(--text-opacity))"],
  "&:hover": {
    "--text-opacity": "1",
    "--bg-opacity": "1",
    color: ["#def7ec", "rgba(222, 247, 236, var(--text-opacity))"],
    backgroundColor: ["#c3ddfd", "rgba(195, 221, 253, var(--bg-opacity))"],
  },
};

Transform to CSS string syntax with the css plugin:

const styles = `
  --text-opacity: 1;
  color: #fde8e8;
  color: rgba(253, 232, 232, var(--text-opacity));
  &:hover {
    --text-opacity: 1;
    --bg-opacity: 1;
    color: #def7ec;
    color: rgba(222, 247, 236, var(--text-opacity));
    background-color: #c3ddfd;
    background-color: rgba(195, 221, 253, var(--bg-opacity));
  }
`;

objectstyles plugins make it possible to support any CSS-in-JS library syntax.

Output mode "classes" example

import xw from "xwind";

const styles = xw`text-red-100 hover:text-green-100 hover:bg-blue-200`;
// OR (with custom array syntax)
const styles = xw`text-red-100 hover[text-green-100 bg-blue-200]`;

Transforms into a classes string:

const styles = "text-red-100 hover:text-green-100 hover:bg-blue-200";

Generate the css output with with the xwind cli:

npx run xwind

Output file "/src/styles/xwind.css":

/*! Generated with xwind | https://github.com/arthie/xwind */
.hover\:bg-blue-200:hover {
  --tw-bg-opacity: 1;
  background-color: rgba(191, 219, 254, var(--tw-bg-opacity));
}
.text-red-100 {
  --tw-text-opacity: 1;
  color: rgba(254, 226, 226, var(--tw-text-opacity));
}
.hover\:text-green-100:hover {
  --tw-text-opacity: 1;
  color: rgba(220, 252, 231, var(--tw-text-opacity));
}

Full xwind package documentation


typescript-xwind-plugin

This package is a typescript language service plugin that adds editor support for xwind tagged template syntax: xw`...` or tw`...`

autocomplete


xwind VS Code extension

This extension activates typescript-xwind-plugin inside VS Code's Typescript language service.

Developer packages

Want to create Tailwind tools with javascript? Have a look at these packages they make the xwind and typescript-xwind-plugin possible.

@xwind/class-utilities

The class-utilities package contains flexible utilities to compose and parse Tailwind classes.


@xwind/core (WIP)

The core package uses Tailwind internals to extracts/generate all the data you could want from Tailwind. It provides the data in a structured way with the necessary utilities to create and manipulate this data.

Non-Affiliation disclaimer

This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Tailwind Labs Inc., or any of its subsidiaries or its affiliates. The name Tailwind as well as related names, marks, emblems and images are registered trademarks of their respective owners.

The official Tailwind website can be found at https://tailwindcss.com/.

Please contact the project ower if there are any concerns regarding: Tailwind CSS brand assets and usage guidelines.


Author: Arthie
Source Code: https://github.com/Arthie/xwind
License: MIT License

#tailwindcss #javascrip 

Tailwind CSS As A Templating Language in JS and CSS-in-JS

Fast Portable Noise Library: C# C++ C Java(Script) HLSL

FastNoise Lite

FastNoise Lite is an extremely portable open source noise generation library with a large selection of noise algorithms. This library focuses on high performance while avoiding platform/language specific features, allowing for easy ports to as many possible languages.

This project is an evolution of the original FastNoise library and shares the same goal: An easy to use library that can quickly be integrated into a project and provides performant modern noise generation. See a breakdown of changes from the transition to FastNoise Lite here

If you are looking for a more extensive noise generation library consider using FastNoise2. It provides large performance gains thanks to SIMD and uses a node graph structure to allow complex noise configurations with lots of flexibility.

Features

  • 2D & 3D
  • OpenSimplex2 Noise
  • OpenSimplex2S Noise
  • Cellular (Voronoi) Noise
  • Perlin Noise
  • Value Noise
  • Value Cubic Noise
  • OpenSimplex2-based Domain Warp
  • Basic Grid Gradient Domain Warp
  • Multiple fractal options for all of the above
  • Supports floats and/or doubles

Supported Languages

Getting Started

Documentation

FastNoise Lite Preview App

A compact testing application is available for testing all features included in FastNoise Lite with a visual representation. This can be used for development purposes and testing noise settings.

Download links can be found in the Releases. Build instructions can be found in the PreviewApp directory.

FastNoise GUI

Performance Comparisons

Benchmarked using C++ version with NoiseBenchmarking

  • CPU: Intel 7820X @ 4.9Ghz
  • OS: Win10 x64
  • Compiler: clang-cl 10.0.0 -m64 /O2

Million points of noise generated per second (higher = better)

3DValuePerlin(*Open)SimplexCellular
FastNoise Lite64.1347.9336.83*12.49
FastNoise (Legacy)49.3437.7544.7413.27
FastNoise 2 (AVX2)494.49261.10268.4452.43
libnoise 27.35 0.65
stb perlin 34.32  
2DValuePerlinSimplexCellular
FastNoise Lite114.0192.8371.3039.15
FastNoise (Legacy)102.1287.9965.2936.84
FastNoise 2 (AVX2)776.33624.27466.03194.30

Credits:

  • OpenSimplex2 for the OpenSimplex2 noise algorithm
  • @KdotJPG for implementing all the OpenSimplex alogrithms and the Java port
  • CubicNoise for the Value (Cubic) noise algorithm
  • @Rover656 for creating the preview GUI and porting FastNoise Lite to C and HLSL.

Examples

Ridged Fractal

Cellular

Cellular Fractal

Cellular Warped

Value Warped

Cellular Scrolling

OpenSimplex2 Warp

 


Author: Auburn
Source Code: https://github.com/Auburn/FastNoiseLite
License: MIT License

#csharp #cpluplus #javascrip 

Fast Portable Noise Library: C# C++ C Java(Script) HLSL

A Pure Node.js JavaScript Client Implementing The MySQL Protocol.

Mysql

Install

This is a Node.js module available through the npm registry.

Before installing, download and install Node.js. Node.js 0.6 or higher is required.

Installation is done using the npm install command:

$ npm install mysql

For information about the previous 0.9.x releases, visit the v0.9 branch.

Sometimes I may also ask you to install the latest version from Github to check if a bugfix is working. In this case, please do:

$ npm install mysqljs/mysql

Introduction

This is a node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed.

Here is an example on how to use it:

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'me',
  password : 'secret',
  database : 'my_db'
});

connection.connect();

connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});

connection.end();

From this example, you can learn the following:

  • Every method you invoke on a connection is queued and executed in sequence.
  • Closing the connection is done using end() which makes sure all remaining queries are executed before sending a quit packet to the mysql server.

Contributors

Thanks goes to the people who have contributed code to this module, see the GitHub Contributors page.

Additionally I'd like to thank the following people:

  • Andrey Hristov (Oracle) - for helping me with protocol questions.
  • Ulf Wendel (Oracle) - for helping me with protocol questions.

Sponsors

The following companies have supported this project financially, allowing me to spend more time on it (ordered by time of contribution):

Community

If you'd like to discuss this module, or ask questions about it, please use one of the following:

Establishing connections

The recommended way to establish a connection is this:

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'example.org',
  user     : 'bob',
  password : 'secret'
});

connection.connect(function(err) {
  if (err) {
    console.error('error connecting: ' + err.stack);
    return;
  }

  console.log('connected as id ' + connection.threadId);
});

However, a connection can also be implicitly established by invoking a query:

var mysql      = require('mysql');
var connection = mysql.createConnection(...);

connection.query('SELECT 1', function (error, results, fields) {
  if (error) throw error;
  // connected!
});

Depending on how you like to handle your errors, either method may be appropriate. Any type of connection error (handshake or network) is considered a fatal error, see the Error Handling section for more information.

Connection options

When establishing a connection, you can set the following options:

  • host: The hostname of the database you are connecting to. (Default: localhost)
  • port: The port number to connect to. (Default: 3306)
  • localAddress: The source IP address to use for TCP connection. (Optional)
  • socketPath: The path to a unix domain socket to connect to. When used host and port are ignored.
  • user: The MySQL user to authenticate as.
  • password: The password of that MySQL user.
  • database: Name of the database to use for this connection (Optional).
  • charset: The charset for the connection. This is called "collation" in the SQL-level of MySQL (like utf8_general_ci). If a SQL-level charset is specified (like utf8mb4) then the default collation for that charset is used. (Default: 'UTF8_GENERAL_CI')
  • timezone: The timezone configured on the MySQL server. This is used to type cast server date/time values to JavaScript Date object and vice versa. This can be 'local', 'Z', or an offset in the form +HH:MM or -HH:MM. (Default: 'local')
  • connectTimeout: The milliseconds before a timeout occurs during the initial connection to the MySQL server. (Default: 10000)
  • stringifyObjects: Stringify objects instead of converting to values. (Default: false)
  • insecureAuth: Allow connecting to MySQL instances that ask for the old (insecure) authentication method. (Default: false)
  • typeCast: Determines if column values should be converted to native JavaScript types. (Default: true)
  • queryFormat: A custom query format function. See Custom format.
  • supportBigNumbers: When dealing with big numbers (BIGINT and DECIMAL columns) in the database, you should enable this option (Default: false).
  • bigNumberStrings: Enabling both supportBigNumbers and bigNumberStrings forces big numbers (BIGINT and DECIMAL columns) to be always returned as JavaScript String objects (Default: false). Enabling supportBigNumbers but leaving bigNumberStrings disabled will return big numbers as String objects only when they cannot be accurately represented with [JavaScript Number objects] (https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type) (which happens when they exceed the [-2^53, +2^53] range), otherwise they will be returned as Number objects. This option is ignored if supportBigNumbers is disabled.
  • dateStrings: Force date types (TIMESTAMP, DATETIME, DATE) to be returned as strings rather than inflated into JavaScript Date objects. Can be true/false or an array of type names to keep as strings. (Default: false)
  • debug: Prints protocol details to stdout. Can be true/false or an array of packet type names that should be printed. (Default: false)
  • trace: Generates stack traces on Error to include call site of library entrance ("long stack traces"). Slight performance penalty for most calls. (Default: true)
  • localInfile: Allow LOAD DATA INFILE to use the LOCAL modifier. (Default: true)
  • multipleStatements: Allow multiple mysql statements per query. Be careful with this, it could increase the scope of SQL injection attacks. (Default: false)
  • flags: List of connection flags to use other than the default ones. It is also possible to blacklist default ones. For more information, check Connection Flags.
  • ssl: object with ssl parameters or a string containing name of ssl profile. See SSL options.

In addition to passing these options as an object, you can also use a url string. For example:

var connection = mysql.createConnection('mysql://user:pass@host/db?debug=true&charset=BIG5_CHINESE_CI&timezone=-0700');

Note: The query values are first attempted to be parsed as JSON, and if that fails assumed to be plaintext strings.

SSL options

The ssl option in the connection options takes a string or an object. When given a string, it uses one of the predefined SSL profiles included. The following profiles are included:

When connecting to other servers, you will need to provide an object with any of the following options:

Here is a simple example:

var connection = mysql.createConnection({
  host : 'localhost',
  ssl  : {
    ca : fs.readFileSync(__dirname + '/mysql-ca.crt')
  }
});

You can also connect to a MySQL server without properly providing the appropriate CA to trust. You should not do this.

var connection = mysql.createConnection({
  host : 'localhost',
  ssl  : {
    // DO NOT DO THIS
    // set up your ca correctly to trust the connection
    rejectUnauthorized: false
  }
});

Connection flags

If, for any reason, you would like to change the default connection flags, you can use the connection option flags. Pass a string with a comma separated list of items to add to the default flags. If you don't want a default flag to be used prepend the flag with a minus sign. To add a flag that is not in the default list, just write the flag name, or prefix it with a plus (case insensitive).

var connection = mysql.createConnection({
  // disable FOUND_ROWS flag, enable IGNORE_SPACE flag
  flags: '-FOUND_ROWS,IGNORE_SPACE'
});

The following flags are available:

  • COMPRESS - Enable protocol compression. This feature is not currently supported by the Node.js implementation so cannot be turned on. (Default off)
  • CONNECT_WITH_DB - Ability to specify the database on connection. (Default on)
  • FOUND_ROWS - Send the found rows instead of the affected rows as affectedRows. (Default on)
  • IGNORE_SIGPIPE - Don't issue SIGPIPE if network failures. This flag has no effect on this Node.js implementation. (Default on)
  • IGNORE_SPACE - Let the parser ignore spaces before the ( in queries. (Default on)
  • INTERACTIVE - Indicates to the MySQL server this is an "interactive" client. This will use the interactive timeouts on the MySQL server and report as interactive in the process list. (Default off)
  • LOCAL_FILES - Can use LOAD DATA LOCAL. This flag is controlled by the connection option localInfile. (Default on)
  • LONG_FLAG - Longer flags in Protocol::ColumnDefinition320. (Default on)
  • LONG_PASSWORD - Use the improved version of Old Password Authentication. (Default on)
  • MULTI_RESULTS - Can handle multiple resultsets for queries. (Default on)
  • MULTI_STATEMENTS - The client may send multiple statement per query or statement prepare (separated by ;). This flag is controlled by the connection option multipleStatements. (Default off)
  • NO_SCHEMA
  • ODBC Special handling of ODBC behaviour. This flag has no effect on this Node.js implementation. (Default on)
  • PLUGIN_AUTH - Uses the plugin authentication mechanism when connecting to the MySQL server. This feature is not currently supported by the Node.js implementation so cannot be turned on. (Default off)
  • PROTOCOL_41 - Uses the 4.1 protocol. (Default on)
  • PS_MULTI_RESULTS - Can handle multiple resultsets for execute. (Default on)
  • REMEMBER_OPTIONS - This is specific to the C client, and has no effect on this Node.js implementation. (Default off)
  • RESERVED - Old flag for the 4.1 protocol. (Default on)
  • SECURE_CONNECTION - Support native 4.1 authentication. (Default on)
  • SSL - Use SSL after handshake to encrypt data in transport. This feature is controlled though the ssl connection option, so the flag has no effect. (Default off)
  • SSL_VERIFY_SERVER_CERT - Verify the server certificate during SSL set up. This feature is controlled though the ssl.rejectUnauthorized connection option, so the flag has no effect. (Default off)
  • TRANSACTIONS - Asks for the transaction status flags. (Default on)

Terminating connections

There are two ways to end a connection. Terminating a connection gracefully is done by calling the end() method:

connection.end(function(err) {
  // The connection is terminated now
});

This will make sure all previously enqueued queries are still executed before sending a COM_QUIT packet to the MySQL server. If a fatal error occurs before the COM_QUIT packet can be sent, an err argument will be provided to the callback, but the connection will be terminated regardless of that.

An alternative way to end the connection is to call the destroy() method. This will cause an immediate termination of the underlying socket. Additionally destroy() guarantees that no more events or callbacks will be triggered for the connection.

connection.destroy();

Unlike end() the destroy() method does not take a callback argument.

Pooling connections

Rather than creating and managing connections one-by-one, this module also provides built-in connection pooling using mysql.createPool(config). Read more about connection pooling.

Create a pool and use it directly:

var mysql = require('mysql');
var pool  = mysql.createPool({
  connectionLimit : 10,
  host            : 'example.org',
  user            : 'bob',
  password        : 'secret',
  database        : 'my_db'
});

pool.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});

This is a shortcut for the pool.getConnection() -> connection.query() -> connection.release() code flow. Using pool.getConnection() is useful to share connection state for subsequent queries. This is because two calls to pool.query() may use two different connections and run in parallel. This is the basic structure:

var mysql = require('mysql');
var pool  = mysql.createPool(...);

pool.getConnection(function(err, connection) {
  if (err) throw err; // not connected!

  // Use the connection
  connection.query('SELECT something FROM sometable', function (error, results, fields) {
    // When done with the connection, release it.
    connection.release();

    // Handle error after the release.
    if (error) throw error;

    // Don't use the connection here, it has been returned to the pool.
  });
});

If you would like to close the connection and remove it from the pool, use connection.destroy() instead. The pool will create a new connection the next time one is needed.

Connections are lazily created by the pool. If you configure the pool to allow up to 100 connections, but only ever use 5 simultaneously, only 5 connections will be made. Connections are also cycled round-robin style, with connections being taken from the top of the pool and returning to the bottom.

When a previous connection is retrieved from the pool, a ping packet is sent to the server to check if the connection is still good.

Pool options

Pools accept all the same options as a connection. When creating a new connection, the options are simply passed to the connection constructor. In addition to those options pools accept a few extras:

  • acquireTimeout: The milliseconds before a timeout occurs during the connection acquisition. This is slightly different from connectTimeout, because acquiring a pool connection does not always involve making a connection. If a connection request is queued, the time the request spends in the queue does not count towards this timeout. (Default: 10000)
  • waitForConnections: Determines the pool's action when no connections are available and the limit has been reached. If true, the pool will queue the connection request and call it when one becomes available. If false, the pool will immediately call back with an error. (Default: true)
  • connectionLimit: The maximum number of connections to create at once. (Default: 10)
  • queueLimit: The maximum number of connection requests the pool will queue before returning an error from getConnection. If set to 0, there is no limit to the number of queued connection requests. (Default: 0)

Pool events

acquire

The pool will emit an acquire event when a connection is acquired from the pool. This is called after all acquiring activity has been performed on the connection, right before the connection is handed to the callback of the acquiring code.

pool.on('acquire', function (connection) {
  console.log('Connection %d acquired', connection.threadId);
});

connection

The pool will emit a connection event when a new connection is made within the pool. If you need to set session variables on the connection before it gets used, you can listen to the connection event.

pool.on('connection', function (connection) {
  connection.query('SET SESSION auto_increment_increment=1')
});

enqueue

The pool will emit an enqueue event when a callback has been queued to wait for an available connection.

pool.on('enqueue', function () {
  console.log('Waiting for available connection slot');
});

release

The pool will emit a release event when a connection is released back to the pool. This is called after all release activity has been performed on the connection, so the connection will be listed as free at the time of the event.

pool.on('release', function (connection) {
  console.log('Connection %d released', connection.threadId);
});

Closing all the connections in a pool

When you are done using the pool, you have to end all the connections or the Node.js event loop will stay active until the connections are closed by the MySQL server. This is typically done if the pool is used in a script or when trying to gracefully shutdown a server. To end all the connections in the pool, use the end method on the pool:

pool.end(function (err) {
  // all connections in the pool have ended
});

The end method takes an optional callback that you can use to know when all the connections are ended.

Once pool.end is called, pool.getConnection and other operations can no longer be performed. Wait until all connections in the pool are released before calling pool.end. If you use the shortcut method pool.query, in place of pool.getConnectionconnection.queryconnection.release, wait until it completes.

pool.end calls connection.end on every active connection in the pool. This queues a QUIT packet on the connection and sets a flag to prevent pool.getConnection from creating new connections. All commands / queries already in progress will complete, but new commands won't execute.

PoolCluster

PoolCluster provides multiple hosts connection. (group & retry & selector)

// create
var poolCluster = mysql.createPoolCluster();

// add configurations (the config is a pool config object)
poolCluster.add(config); // add configuration with automatic name
poolCluster.add('MASTER', masterConfig); // add a named configuration
poolCluster.add('SLAVE1', slave1Config);
poolCluster.add('SLAVE2', slave2Config);

// remove configurations
poolCluster.remove('SLAVE2'); // By nodeId
poolCluster.remove('SLAVE*'); // By target group : SLAVE1-2

// Target Group : ALL(anonymous, MASTER, SLAVE1-2), Selector : round-robin(default)
poolCluster.getConnection(function (err, connection) {});

// Target Group : MASTER, Selector : round-robin
poolCluster.getConnection('MASTER', function (err, connection) {});

// Target Group : SLAVE1-2, Selector : order
// If can't connect to SLAVE1, return SLAVE2. (remove SLAVE1 in the cluster)
poolCluster.on('remove', function (nodeId) {
  console.log('REMOVED NODE : ' + nodeId); // nodeId = SLAVE1
});

// A pattern can be passed with *  as wildcard
poolCluster.getConnection('SLAVE*', 'ORDER', function (err, connection) {});

// The pattern can also be a regular expression
poolCluster.getConnection(/^SLAVE[12]$/, function (err, connection) {});

// of namespace : of(pattern, selector)
poolCluster.of('*').getConnection(function (err, connection) {});

var pool = poolCluster.of('SLAVE*', 'RANDOM');
pool.getConnection(function (err, connection) {});
pool.getConnection(function (err, connection) {});
pool.query(function (error, results, fields) {});

// close all connections
poolCluster.end(function (err) {
  // all connections in the pool cluster have ended
});

PoolCluster options

  • canRetry: If true, PoolCluster will attempt to reconnect when connection fails. (Default: true)
  • removeNodeErrorCount: If connection fails, node's errorCount increases. When errorCount is greater than removeNodeErrorCount, remove a node in the PoolCluster. (Default: 5)
  • restoreNodeTimeout: If connection fails, specifies the number of milliseconds before another connection attempt will be made. If set to 0, then node will be removed instead and never re-used. (Default: 0)
  • defaultSelector: The default selector. (Default: RR)
    • RR: Select one alternately. (Round-Robin)
    • RANDOM: Select the node by random function.
    • ORDER: Select the first node available unconditionally.
var clusterConfig = {
  removeNodeErrorCount: 1, // Remove the node immediately when connection fails.
  defaultSelector: 'ORDER'
};

var poolCluster = mysql.createPoolCluster(clusterConfig);

Switching users and altering connection state

MySQL offers a changeUser command that allows you to alter the current user and other aspects of the connection without shutting down the underlying socket:

connection.changeUser({user : 'john'}, function(err) {
  if (err) throw err;
});

The available options for this feature are:

  • user: The name of the new user (defaults to the previous one).
  • password: The password of the new user (defaults to the previous one).
  • charset: The new charset (defaults to the previous one).
  • database: The new database (defaults to the previous one).

A sometimes useful side effect of this functionality is that this function also resets any connection state (variables, transactions, etc.).

Errors encountered during this operation are treated as fatal connection errors by this module.

Server disconnects

You may lose the connection to a MySQL server due to network problems, the server timing you out, the server being restarted, or crashing. All of these events are considered fatal errors, and will have the err.code = 'PROTOCOL_CONNECTION_LOST'. See the Error Handling section for more information.

Re-connecting a connection is done by establishing a new connection. Once terminated, an existing connection object cannot be re-connected by design.

With Pool, disconnected connections will be removed from the pool freeing up space for a new connection to be created on the next getConnection call.

With PoolCluster, disconnected connections will count as errors against the related node, incrementing the error code for that node. Once there are more than removeNodeErrorCount errors on a given node, it is removed from the cluster. When this occurs, the PoolCluster may emit a POOL_NONEONLINE error if there are no longer any matching nodes for the pattern. The restoreNodeTimeout config can be set to restore offline nodes after a given timeout.

Performing queries

The most basic way to perform a query is to call the .query() method on an object (like a Connection, Pool, or PoolNamespace instance).

The simplest form of .query() is .query(sqlString, callback), where a SQL string is the first argument and the second is a callback:

connection.query('SELECT * FROM `books` WHERE `author` = "David"', function (error, results, fields) {
  // error will be an Error if one occurred during the query
  // results will contain the results of the query
  // fields will contain information about the returned results fields (if any)
});

The second form .query(sqlString, values, callback) comes when using placeholder values (see escaping query values):

connection.query('SELECT * FROM `books` WHERE `author` = ?', ['David'], function (error, results, fields) {
  // error will be an Error if one occurred during the query
  // results will contain the results of the query
  // fields will contain information about the returned results fields (if any)
});

The third form .query(options, callback) comes when using various advanced options on the query, like escaping query values, joins with overlapping column names, timeouts, and type casting.

connection.query({
  sql: 'SELECT * FROM `books` WHERE `author` = ?',
  timeout: 40000, // 40s
  values: ['David']
}, function (error, results, fields) {
  // error will be an Error if one occurred during the query
  // results will contain the results of the query
  // fields will contain information about the returned results fields (if any)
});

Note that a combination of the second and third forms can be used where the placeholder values are passed as an argument and not in the options object. The values argument will override the values in the option object.

connection.query({
    sql: 'SELECT * FROM `books` WHERE `author` = ?',
    timeout: 40000, // 40s
  },
  ['David'],
  function (error, results, fields) {
    // error will be an Error if one occurred during the query
    // results will contain the results of the query
    // fields will contain information about the returned results fields (if any)
  }
);

If the query only has a single replacement character (?), and the value is not null, undefined, or an array, it can be passed directly as the second argument to .query:

connection.query(
  'SELECT * FROM `books` WHERE `author` = ?',
  'David',
  function (error, results, fields) {
    // error will be an Error if one occurred during the query
    // results will contain the results of the query
    // fields will contain information about the returned results fields (if any)
  }
);

Escaping query values

Caution These methods of escaping values only works when the NO_BACKSLASH_ESCAPES SQL mode is disabled (which is the default state for MySQL servers).

Caution This library performs client-side escaping, as this is a library to generate SQL strings on the client side. The syntax for functions like mysql.format may look similar to a prepared statement, but it is not and the escaping rules from this module are used to generate a resulting SQL string. The purpose of escaping input is to avoid SQL Injection attacks. In order to support enhanced support like SET and IN formatting, this module will escape based on the shape of the passed in JavaScript value, and the resulting escaped string may be more than a single value. When structured user input is provided as the value to escape, care should be taken to validate the shape of the input to validate the output will be what is expected.

In order to avoid SQL Injection attacks, you should always escape any user provided data before using it inside a SQL query. You can do so using the mysql.escape(), connection.escape() or pool.escape() methods:

var userId = 'some user provided value';
var sql    = 'SELECT * FROM users WHERE id = ' + connection.escape(userId);
connection.query(sql, function (error, results, fields) {
  if (error) throw error;
  // ...
});

Alternatively, you can use ? characters as placeholders for values you would like to have escaped like this:

connection.query('SELECT * FROM users WHERE id = ?', [userId], function (error, results, fields) {
  if (error) throw error;
  // ...
});

Multiple placeholders are mapped to values in the same order as passed. For example, in the following query foo equals a, bar equals b, baz equals c, and id will be userId:

connection.query('UPDATE users SET foo = ?, bar = ?, baz = ? WHERE id = ?', ['a', 'b', 'c', userId], function (error, results, fields) {
  if (error) throw error;
  // ...
});

This looks similar to prepared statements in MySQL, however it really just uses the same connection.escape() method internally.

Caution This also differs from prepared statements in that all ? are replaced, even those contained in comments and strings.

Different value types are escaped differently, here is how:

  • Numbers are left untouched
  • Booleans are converted to true / false
  • Date objects are converted to 'YYYY-mm-dd HH:ii:ss' strings
  • Buffers are converted to hex strings, e.g. X'0fa5'
  • Strings are safely escaped
  • Arrays are turned into list, e.g. ['a', 'b'] turns into 'a', 'b'
  • Nested arrays are turned into grouped lists (for bulk inserts), e.g. [['a', 'b'], ['c', 'd']] turns into ('a', 'b'), ('c', 'd')
  • Objects that have a toSqlString method will have .toSqlString() called and the returned value is used as the raw SQL.
  • Objects are turned into key = 'val' pairs for each enumerable property on the object. If the property's value is a function, it is skipped; if the property's value is an object, toString() is called on it and the returned value is used.
  • undefined / null are converted to NULL
  • NaN / Infinity are left as-is. MySQL does not support these, and trying to insert them as values will trigger MySQL errors until they implement support.

This escaping allows you to do neat things like this:

var post  = {id: 1, title: 'Hello MySQL'};
var query = connection.query('INSERT INTO posts SET ?', post, function (error, results, fields) {
  if (error) throw error;
  // Neat!
});
console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'

And the toSqlString method allows you to form complex queries with functions:

var CURRENT_TIMESTAMP = { toSqlString: function() { return 'CURRENT_TIMESTAMP()'; } };
var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42

To generate objects with a toSqlString method, the mysql.raw() method can be used. This creates an object that will be left un-touched when using in a ? placeholder, useful for using functions as dynamic values:

Caution The string provided to mysql.raw() will skip all escaping functions when used, so be careful when passing in unvalidated input.

var CURRENT_TIMESTAMP = mysql.raw('CURRENT_TIMESTAMP()');
var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42

If you feel the need to escape queries by yourself, you can also use the escaping function directly:

var query = "SELECT * FROM posts WHERE title=" + mysql.escape("Hello MySQL");

console.log(query); // SELECT * FROM posts WHERE title='Hello MySQL'

Escaping query identifiers

If you can't trust an SQL identifier (database / table / column name) because it is provided by a user, you should escape it with mysql.escapeId(identifier), connection.escapeId(identifier) or pool.escapeId(identifier) like this:

var sorter = 'date';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter);
connection.query(sql, function (error, results, fields) {
  if (error) throw error;
  // ...
});

It also supports adding qualified identifiers. It will escape both parts.

var sorter = 'date';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId('posts.' + sorter);
// -> SELECT * FROM posts ORDER BY `posts`.`date`

If you do not want to treat . as qualified identifiers, you can set the second argument to true in order to keep the string as a literal identifier:

var sorter = 'date.2';
var sql    = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter, true);
// -> SELECT * FROM posts ORDER BY `date.2`

Alternatively, you can use ?? characters as placeholders for identifiers you would like to have escaped like this:

var userId = 1;
var columns = ['username', 'email'];
var query = connection.query('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId], function (error, results, fields) {
  if (error) throw error;
  // ...
});

console.log(query.sql); // SELECT `username`, `email` FROM `users` WHERE id = 1

Please note that this last character sequence is experimental and syntax might change

When you pass an Object to .escape() or .query(), .escapeId() is used to avoid SQL injection in object keys.

Preparing Queries

You can use mysql.format to prepare a query with multiple insertion points, utilizing the proper escaping for ids and values. A simple example of this follows:

var sql = "SELECT * FROM ?? WHERE ?? = ?";
var inserts = ['users', 'id', userId];
sql = mysql.format(sql, inserts);

Following this you then have a valid, escaped query that you can then send to the database safely. This is useful if you are looking to prepare the query before actually sending it to the database. As mysql.format is exposed from SqlString.format you also have the option (but are not required) to pass in stringifyObject and timezone, allowing you provide a custom means of turning objects into strings, as well as a location-specific/timezone-aware Date.

Custom format

If you prefer to have another type of query escape format, there's a connection configuration option you can use to define a custom format function. You can access the connection object if you want to use the built-in .escape() or any other connection function.

Here's an example of how to implement another format:

connection.config.queryFormat = function (query, values) {
  if (!values) return query;
  return query.replace(/\:(\w+)/g, function (txt, key) {
    if (values.hasOwnProperty(key)) {
      return this.escape(values[key]);
    }
    return txt;
  }.bind(this));
};

connection.query("UPDATE posts SET title = :title", { title: "Hello MySQL" });

Getting the id of an inserted row

If you are inserting a row into a table with an auto increment primary key, you can retrieve the insert id like this:

connection.query('INSERT INTO posts SET ?', {title: 'test'}, function (error, results, fields) {  if (error) throw error;  console.log(results.insertId); });

When dealing with big numbers (above JavaScript Number precision limit), you should consider enabling supportBigNumbers option to be able to read the insert id as a string, otherwise it will throw an error.

This option is also required when fetching big numbers from the database, otherwise you will get values rounded to hundreds or thousands due to the precision limit.

Getting the number of affected rows

You can get the number of affected rows from an insert, update or delete statement.

connection.query('DELETE FROM posts WHERE title = "wrong"', function (error, results, fields) {
  if (error) throw error;
  console.log('deleted ' + results.affectedRows + ' rows');
})

Getting the number of changed rows

You can get the number of changed rows from an update statement.

"changedRows" differs from "affectedRows" in that it does not count updated rows whose values were not changed.

connection.query('UPDATE posts SET ...', function (error, results, fields) {
  if (error) throw error;
  console.log('changed ' + results.changedRows + ' rows');
})

Getting the connection ID

You can get the MySQL connection ID ("thread ID") of a given connection using the threadId property.

connection.connect(function(err) {
  if (err) throw err;
  console.log('connected as id ' + connection.threadId);
});

Executing queries in parallel

The MySQL protocol is sequential, this means that you need multiple connections to execute queries in parallel. You can use a Pool to manage connections, one simple approach is to create one connection per incoming http request.

Streaming query rows

Sometimes you may want to select large quantities of rows and process each of them as they are received. This can be done like this:

var query = connection.query('SELECT * FROM posts');
query
  .on('error', function(err) {
    // Handle error, an 'end' event will be emitted after this as well
  })
  .on('fields', function(fields) {
    // the field packets for the rows to follow
  })
  .on('result', function(row) {
    // Pausing the connnection is useful if your processing involves I/O
    connection.pause();

    processRow(row, function() {
      connection.resume();
    });
  })
  .on('end', function() {
    // all rows have been received
  });

Please note a few things about the example above:

  • Usually you will want to receive a certain amount of rows before starting to throttle the connection using pause(). This number will depend on the amount and size of your rows.
  • pause() / resume() operate on the underlying socket and parser. You are guaranteed that no more 'result' events will fire after calling pause().
  • You MUST NOT provide a callback to the query() method when streaming rows.
  • The 'result' event will fire for both rows as well as OK packets confirming the success of a INSERT/UPDATE query.
  • It is very important not to leave the result paused too long, or you may encounter Error: Connection lost: The server closed the connection. The time limit for this is determined by the net_write_timeout setting on your MySQL server.

Additionally you may be interested to know that it is currently not possible to stream individual row columns, they will always be buffered up entirely. If you have a good use case for streaming large fields to and from MySQL, I'd love to get your thoughts and contributions on this.

Piping results with Streams

The query object provides a convenience method .stream([options]) that wraps query events into a Readable Stream object. This stream can easily be piped downstream and provides automatic pause/resume, based on downstream congestion and the optional highWaterMark. The objectMode parameter of the stream is set to true and cannot be changed (if you need a byte stream, you will need to use a transform stream, like objstream for example).

For example, piping query results into another stream (with a max buffer of 5 objects) is simply:

connection.query('SELECT * FROM posts')
  .stream({highWaterMark: 5})
  .pipe(...);

Multiple statement queries

Support for multiple statements is disabled for security reasons (it allows for SQL injection attacks if values are not properly escaped). To use this feature you have to enable it for your connection:

var connection = mysql.createConnection({multipleStatements: true});

Once enabled, you can execute multiple statement queries like any other query:

connection.query('SELECT 1; SELECT 2', function (error, results, fields) {
  if (error) throw error;
  // `results` is an array with one element for every statement in the query:
  console.log(results[0]); // [{1: 1}]
  console.log(results[1]); // [{2: 2}]
});

Additionally you can also stream the results of multiple statement queries:

var query = connection.query('SELECT 1; SELECT 2');

query
  .on('fields', function(fields, index) {
    // the fields for the result rows that follow
  })
  .on('result', function(row, index) {
    // index refers to the statement this result belongs to (starts at 0)
  });

If one of the statements in your query causes an error, the resulting Error object contains a err.index property which tells you which statement caused it. MySQL will also stop executing any remaining statements when an error occurs.

Please note that the interface for streaming multiple statement queries is experimental and I am looking forward to feedback on it.

Stored procedures

You can call stored procedures from your queries as with any other mysql driver. If the stored procedure produces several result sets, they are exposed to you the same way as the results for multiple statement queries.

Joins with overlapping column names

When executing joins, you are likely to get result sets with overlapping column names.

By default, node-mysql will overwrite colliding column names in the order the columns are received from MySQL, causing some of the received values to be unavailable.

However, you can also specify that you want your columns to be nested below the table name like this:

var options = {sql: '...', nestTables: true};
connection.query(options, function (error, results, fields) {
  if (error) throw error;
  /* results will be an array like this now:
  [{
    table1: {
      fieldA: '...',
      fieldB: '...',
    },
    table2: {
      fieldA: '...',
      fieldB: '...',
    },
  }, ...]
  */
});

Or use a string separator to have your results merged.

var options = {sql: '...', nestTables: '_'};
connection.query(options, function (error, results, fields) {
  if (error) throw error;
  /* results will be an array like this now:
  [{
    table1_fieldA: '...',
    table1_fieldB: '...',
    table2_fieldA: '...',
    table2_fieldB: '...',
  }, ...]
  */
});

Transactions

Simple transaction support is available at the connection level:

connection.beginTransaction(function(err) {
  if (err) { throw err; }
  connection.query('INSERT INTO posts SET title=?', title, function (error, results, fields) {
    if (error) {
      return connection.rollback(function() {
        throw error;
      });
    }

    var log = 'Post ' + results.insertId + ' added';

    connection.query('INSERT INTO log SET data=?', log, function (error, results, fields) {
      if (error) {
        return connection.rollback(function() {
          throw error;
        });
      }
      connection.commit(function(err) {
        if (err) {
          return connection.rollback(function() {
            throw err;
          });
        }
        console.log('success!');
      });
    });
  });
});

Please note that beginTransaction(), commit() and rollback() are simply convenience functions that execute the START TRANSACTION, COMMIT, and ROLLBACK commands respectively. It is important to understand that many commands in MySQL can cause an implicit commit, as described in the MySQL documentation

Ping

A ping packet can be sent over a connection using the connection.ping method. This method will send a ping packet to the server and when the server responds, the callback will fire. If an error occurred, the callback will fire with an error argument.

connection.ping(function (err) {
  if (err) throw err;
  console.log('Server responded to ping');
})

Timeouts

Every operation takes an optional inactivity timeout option. This allows you to specify appropriate timeouts for operations. It is important to note that these timeouts are not part of the MySQL protocol, and rather timeout operations through the client. This means that when a timeout is reached, the connection it occurred on will be destroyed and no further operations can be performed.

// Kill query after 60s
connection.query({sql: 'SELECT COUNT(*) AS count FROM big_table', timeout: 60000}, function (error, results, fields) {
  if (error && error.code === 'PROTOCOL_SEQUENCE_TIMEOUT') {
    throw new Error('too long to count table rows!');
  }

  if (error) {
    throw error;
  }

  console.log(results[0].count + ' rows');
});

Error handling

This module comes with a consistent approach to error handling that you should review carefully in order to write solid applications.

Most errors created by this module are instances of the JavaScript Error object. Additionally they typically come with two extra properties:

  • err.code: String, contains the MySQL server error symbol if the error is a MySQL server error (e.g. 'ER_ACCESS_DENIED_ERROR'), a Node.js error code if it is a Node.js error (e.g. 'ECONNREFUSED'), or an internal error code (e.g. 'PROTOCOL_CONNECTION_LOST').
  • err.errno: Number, contains the MySQL server error number. Only populated from MySQL server error.
  • err.fatal: Boolean, indicating if this error is terminal to the connection object. If the error is not from a MySQL protocol operation, this property will not be defined.
  • err.sql: String, contains the full SQL of the failed query. This can be useful when using a higher level interface like an ORM that is generating the queries.
  • err.sqlState: String, contains the five-character SQLSTATE value. Only populated from MySQL server error.
  • err.sqlMessage: String, contains the message string that provides a textual description of the error. Only populated from MySQL server error.

Fatal errors are propagated to all pending callbacks. In the example below, a fatal error is triggered by trying to connect to a blocked port. Therefore the error object is propagated to both pending callbacks:

var connection = require('mysql').createConnection({
  port: 1 // example blocked port
});

connection.connect(function(err) {
  console.log(err.code); // 'ECONNREFUSED'
  console.log(err.fatal); // true
});

connection.query('SELECT 1', function (error, results, fields) {
  console.log(error.code); // 'ECONNREFUSED'
  console.log(error.fatal); // true
});

Normal errors however are only delegated to the callback they belong to. So in the example below, only the first callback receives an error, the second query works as expected:

connection.query('USE name_of_db_that_does_not_exist', function (error, results, fields) {
  console.log(error.code); // 'ER_BAD_DB_ERROR'
});

connection.query('SELECT 1', function (error, results, fields) {
  console.log(error); // null
  console.log(results.length); // 1
});

Last but not least: If a fatal errors occurs and there are no pending callbacks, or a normal error occurs which has no callback belonging to it, the error is emitted as an 'error' event on the connection object. This is demonstrated in the example below:

connection.on('error', function(err) {
  console.log(err.code); // 'ER_BAD_DB_ERROR'
});

connection.query('USE name_of_db_that_does_not_exist');

Note: 'error' events are special in node. If they occur without an attached listener, a stack trace is printed and your process is killed.

tl;dr: This module does not want you to deal with silent failures. You should always provide callbacks to your method calls. If you want to ignore this advice and suppress unhandled errors, you can do this:

// I am Chuck Norris:
connection.on('error', function() {});

Exception Safety

This module is exception safe. That means you can continue to use it, even if one of your callback functions throws an error which you're catching using 'uncaughtException' or a domain.

Type casting

For your convenience, this driver will cast mysql types into native JavaScript types by default. The default behavior can be changed through various Connection options. The following mappings exist:

Number

  • TINYINT
  • SMALLINT
  • INT
  • MEDIUMINT
  • YEAR
  • FLOAT
  • DOUBLE
  • BIGINT

Date

  • TIMESTAMP
  • DATE
  • DATETIME

Buffer

  • TINYBLOB
  • MEDIUMBLOB
  • LONGBLOB
  • BLOB
  • BINARY
  • VARBINARY
  • BIT (last byte will be filled with 0 bits as necessary)

String

Note text in the binary character set is returned as Buffer, rather than a string.

  • CHAR
  • VARCHAR
  • TINYTEXT
  • MEDIUMTEXT
  • LONGTEXT
  • TEXT
  • ENUM
  • SET
  • DECIMAL (may exceed float precision)
  • TIME (could be mapped to Date, but what date would be set?)
  • GEOMETRY (never used those, get in touch if you do)

It is not recommended (and may go away / change in the future) to disable type casting, but you can currently do so on either the connection:

var connection = require('mysql').createConnection({typeCast: false});

Or on the query level:

var options = {sql: '...', typeCast: false};
var query = connection.query(options, function (error, results, fields) {
  if (error) throw error;
  // ...
});

Custom type casting

You can also pass a function and handle type casting yourself. You're given some column information like database, table and name and also type and length. If you just want to apply a custom type casting to a specific type you can do it and then fallback to the default.

The function is provided two arguments field and next and is expected to return the value for the given field by invoking the parser functions through the field object.

The field argument is a Field object and contains data about the field that need to be parsed. The following are some of the properties on a Field object:

  • db - a string of the database the field came from.
  • table - a string of the table the field came from.
  • name - a string of the field name.
  • type - a string of the field type in all caps.
  • length - a number of the field length, as given by the database.

The next argument is a function that, when called, will return the default type conversion for the given field.

When getting the field data, the following helper methods are present on the field object:

  • .string() - parse the field into a string.
  • .buffer() - parse the field into a Buffer.
  • .geometry() - parse the field as a geometry value.

The MySQL protocol is a text-based protocol. This means that over the wire, all field types are represented as a string, which is why only string-like functions are available on the field object. Based on the type information (like INT), the type cast should convert the string field into a different JavaScript type (like a number).

Here's an example of converting TINYINT(1) to boolean:

connection = mysql.createConnection({
  typeCast: function (field, next) {
    if (field.type === 'TINY' && field.length === 1) {
      return (field.string() === '1'); // 1 = true, 0 = false
    } else {
      return next();
    }
  }
});

WARNING: YOU MUST INVOKE the parser using one of these three field functions in your custom typeCast callback. They can only be called once.

Debugging and reporting problems

If you are running into problems, one thing that may help is enabling the debug mode for the connection:

var connection = mysql.createConnection({debug: true});

This will print all incoming and outgoing packets on stdout. You can also restrict debugging to packet types by passing an array of types to debug:

var connection = mysql.createConnection({debug: ['ComQueryPacket', 'RowDataPacket']});

to restrict debugging to the query and data packets.

If that does not help, feel free to open a GitHub issue. A good GitHub issue will have:

  • The minimal amount of code required to reproduce the problem (if possible)
  • As much debugging output and information about your environment (mysql version, node version, os, etc.) as you can gather.

Security issues

Security issues should not be first reported through GitHub or another public forum, but kept private in order for the collaborators to assess the report and either (a) devise a fix and plan a release date or (b) assert that it is not a security issue (in which case it can be posted in a public forum, like a GitHub issue).

The primary private forum is email, either by emailing the module's author or opening a GitHub issue simply asking to whom a security issues should be addressed to without disclosing the issue or type of issue.

An ideal report would include a clear indication of what the security issue is and how it would be exploited, ideally with an accompanying proof of concept ("PoC") for collaborators to work against and validate potentional fixes against.

Contributing

This project welcomes contributions from the community. Contributions are accepted using GitHub pull requests. If you're not familiar with making GitHub pull requests, please refer to the GitHub documentation "Creating a pull request".

For a good pull request, we ask you provide the following:

  1. Try to include a clear description of your pull request in the description. It should include the basic "what" and "why"s for the request.
  2. The tests should pass as best as you can. See the Running tests section on how to run the different tests. GitHub will automatically run the tests as well, to act as a safety net.
  3. The pull request should include tests for the change. A new feature should have tests for the new feature and bug fixes should include a test that fails without the corresponding code change and passes after they are applied. The command npm run test-cov will generate a coverage/ folder that contains HTML pages of the code coverage, to better understand if everything you're adding is being tested.
  4. If the pull request is a new feature, please be sure to include all appropriate documentation additions in the Readme.md file as well.
  5. To help ensure that your code is similar in style to the existing code, run the command npm run lint and fix any displayed issues.

Running tests

The test suite is split into two parts: unit tests and integration tests. The unit tests run on any machine while the integration tests require a MySQL server instance to be setup.

Running unit tests

$ FILTER=unit npm test

Running integration tests

Set the environment variables MYSQL_DATABASE, MYSQL_HOST, MYSQL_PORT, MYSQL_USER and MYSQL_PASSWORD. MYSQL_SOCKET can also be used in place of MYSQL_HOST and MYSQL_PORT to connect over a UNIX socket. Then run npm test.

For example, if you have an installation of mysql running on localhost:3306 and no password set for the root user, run:

$ mysql -u root -e "CREATE DATABASE IF NOT EXISTS node_mysql_test"
$ MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_DATABASE=node_mysql_test MYSQL_USER=root MYSQL_PASSWORD= FILTER=integration npm test

Todo

  • Prepared statements
  • Support for encodings other than UTF-8 / ASCII

Author: mysqljs
Source Code: https://github.com/mysqljs/mysql
License: MIT License

#mysql #javascrip #nodejs 

A Pure Node.js JavaScript Client Implementing The MySQL Protocol.
Emilie  Okumu

Emilie Okumu

1647626400

Html2pdf.js: Client-side HTML-to-PDF Rendering using Pure JS

html2pdf.js

html2pdf.js converts any webpage or element into a printable PDF entirely client-side using html2canvas and jsPDF.

:warning: There have been several issues reported in v0.10. They are being investigated but in the meantime you may wish to remain on v0.9.3 ("^0.9.3" in npm, or use cdnjs for HTML script tags).

Getting started

CDN

The simplest way to use html2pdf.js is to include it as a script in your HTML by using cdnjs:

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Using a CDN URL will lock you to a specific version, which should ensure stability and give you control over when to change versions. cdnjs gives you access to all past versions of html2pdf.js.

Note: Read about dependences for more information about using the unbundled version dist/html2canvas.min.js.

Raw JS

You may also download dist/html2pdf.bundle.min.js directly to your project folder and include it in your HTML with:

<script src="html2pdf.bundle.min.js"></script>

NPM

Install html2pdf.js and its dependencies using NPM with npm install --save html2pdf.js (make sure to include .js in the package name).

Note: You can use NPM to create your project, but html2pdf.js will not run in Node.js, it must be run in a browser.

Bower

Install html2pdf.js and its dependencies using Bower with bower install --save html2pdf.js (make sure to include .js in the package name).

Console

If you're on a webpage that you can't modify directly and wish to use html2pdf.js to capture a screenshot, you can follow these steps:

  1. Open your browser's console (instructions for different browsers here).
  2. Paste in this code:
function addScript(url) {
    var script = document.createElement('script');
    script.type = 'application/javascript';
    script.src = url;
    document.head.appendChild(script);
}
addScript('https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js');
  1. You may now execute html2pdf.js commands directly from the console. To capture a default PDF of the entire page, use html2pdf(document.body).

Usage

Once installed, html2pdf.js is ready to use. The following command will generate a PDF of #element-to-print and prompt the user to save the result:

var element = document.getElementById('element-to-print');
html2pdf(element);

Advanced usage

Every step of html2pdf.js is configurable, using its new Promise-based API. If html2pdf.js is called without arguments, it will return a Worker object:

var worker = html2pdf();  // Or:  var worker = new html2pdf.Worker;

This worker has methods that can be chained sequentially, as each Promise resolves, and allows insertion of your own intermediate functions between steps. A prerequisite system allows you to skip over mandatory steps (like canvas creation) without any trouble:

// This will implicitly create the canvas and PDF objects before saving.
var worker = html2pdf().from(element).save();

Workflow

The basic workflow of html2pdf.js tasks (enforced by the prereq system) is:

.from() -> .toContainer() -> .toCanvas() -> .toImg() -> .toPdf() -> .save()

Worker API

MethodArgumentsDescription
fromsrc, typeSets the source (HTML string or element) for the PDF. Optional type specifies other sources: 'string', 'element', 'canvas', or 'img'.
totargetConverts the source to the specified target ('container', 'canvas', 'img', or 'pdf'). Each target also has its own toX method that can be called directly: toContainer(), toCanvas(), toImg(), and toPdf().
outputtype, options, srcRoutes to the appropriate outputPdf or outputImg method based on specified src ('pdf' (default) or 'img').
outputPdftype, optionsSends type and options to the jsPDF object's output method, and returns the result as a Promise (use .then to access). See the jsPDF source code for more info.
outputImgtype, optionsReturns the specified data type for the image as a Promise (use .then to access). Supported types: 'img', 'datauristring'/'dataurlstring', and 'datauri'/'dataurl'.
savefilenameSaves the PDF object with the optional filename (creates user download prompt).
setoptSets the specified properties. See Options below for more details.
getkey, cbkReturns the property specified in key, either as a Promise (use .then to access), or by calling cbk if provided.
thenonFulfilled, onRejectedStandard Promise method, with this re-bound to the Worker, and with added progress-tracking (see Progress below). Note that .then returns a Worker, which is a subclass of Promise.
thenCoreonFulFilled, onRejectedStandard Promise method, with this re-bound to the Worker (no progress-tracking). Note that .thenCore returns a Worker, which is a subclass of Promise.
thenExternalonFulfilled, onRejectedTrue Promise method. Using this 'exits' the Worker chain - you will not be able to continue chaining Worker methods after .thenExternal.
catch, catchExternalonRejectedStandard Promise method. catchExternal exits the Worker chain - you will not be able to continue chaining Worker methods after .catchExternal.
errormsgThrows an error in the Worker's Promise chain.

A few aliases are also provided for convenience:

MethodAlias
savesaveAs
setusing
outputexport
thenrun

Options

html2pdf.js can be configured using an optional opt parameter:

var element = document.getElementById('element-to-print');
var opt = {
  margin:       1,
  filename:     'myfile.pdf',
  image:        { type: 'jpeg', quality: 0.98 },
  html2canvas:  { scale: 2 },
  jsPDF:        { unit: 'in', format: 'letter', orientation: 'portrait' }
};

// New Promise-based usage:
html2pdf().set(opt).from(element).save();

// Old monolithic-style usage:
html2pdf(element, opt);

The opt parameter has the following optional fields:

NameTypeDefaultDescription
marginnumber or array0PDF margin (in jsPDF units). Can be a single number, [vMargin, hMargin], or [top, left, bottom, right].
filenamestring'file.pdf'The default filename of the exported PDF.
pagebreakobject{mode: ['css', 'legacy']}Controls the pagebreak behaviour on the page. See Page-breaks below.
imageobject{type: 'jpeg', quality: 0.95}The image type and quality used to generate the PDF. See Image type and quality below.
enableLinksbooleantrueIf enabled, PDF hyperlinks are automatically added ontop of all anchor tags.
html2canvasobject{ }Configuration options sent directly to html2canvas (see here for usage).
jsPDFobject{ }Configuration options sent directly to jsPDF (see here for usage).

Page-breaks

html2pdf.js has the ability to automatically add page-breaks to clean up your document. Page-breaks can be added by CSS styles, set on individual elements using selectors, or avoided from breaking inside all elements (avoid-all mode).

By default, html2pdf.js will respect most CSS break-before, break-after, and break-inside rules, and also add page-breaks after any element with class html2pdf__page-break (for legacy purposes).

Page-break settings

SettingTypeDefaultDescription
modestring or array['css', 'legacy']The mode(s) on which to automatically add page-breaks. One or more of 'avoid-all', 'css', and 'legacy'.
beforestring or array[]CSS selectors for which to add page-breaks before each element. Can be a specific element with an ID ('#myID'), all elements of a type (e.g. 'img'), all of a class ('.myClass'), or even '*' to match every element.
afterstring or array[]Like 'before', but adds a page-break immediately after the element.
avoidstring or array[]Like 'before', but avoids page-breaks on these elements. You can enable this feature on every element using the 'avoid-all' mode.

Page-break modes

ModeDescription
avoid-allAutomatically adds page-breaks to avoid splitting any elements across pages.
cssAdds page-breaks according to the CSS break-before, break-after, and break-inside properties. Only recognizes always/left/right for before/after, and avoid for inside.
legacyAdds page-breaks after elements with class html2pdf__page-break. This feature may be removed in the future.

Example usage

// Avoid page-breaks on all elements, and add one before #page2el.
html2pdf().set({
  pagebreak: { mode: 'avoid-all', before: '#page2el' }
});

// Enable all 'modes', with no explicit elements.
html2pdf().set({
  pagebreak: { mode: ['avoid-all', 'css', 'legacy'] }
});

// No modes, only explicit elements.
html2pdf().set({
  pagebreak: { before: '.beforeClass', after: ['#after1', '#after2'], avoid: 'img' }
});

Image type and quality

You may customize the image type and quality exported from the canvas by setting the image option. This must be an object with the following fields:

NameTypeDefaultDescription
typestring'jpeg'The image type. HTMLCanvasElement only supports 'png', 'jpeg', and 'webp' (on Chrome).
qualitynumber0.95The image quality, from 0 to 1. This setting is only used for jpeg/webp (not png).

These options are limited to the available settings for HTMLCanvasElement.toDataURL(), which ignores quality settings for 'png' images. To enable png image compression, try using the canvas-png-compression shim, which should be an in-place solution to enable png compression via the quality option.

Progress tracking

The Worker object returned by html2pdf() has a built-in progress-tracking mechanism. It will be updated to allow a progress callback that will be called with each update, however it is currently a work-in-progress.

Dependencies

html2pdf.js depends on the external packages html2canvas, jsPDF, and es6-promise. These dependencies are automatically loaded when using NPM or the bundled package.

If using the unbundled dist/html2pdf.min.js (or its un-minified version), you must also include each dependency. Order is important, otherwise html2canvas will be overridden by jsPDF's own internal implementation:

<script src="es6-promise.auto.min.js"></script>
<script src="jspdf.min.js"></script>
<script src="html2canvas.min.js"></script>
<script src="html2pdf.min.js"></script>

Contributing

Issues

When submitting an issue, please provide reproducible code that highlights the issue, preferably by creating a fork of this template jsFiddle (which has html2pdf.js already loaded). Remember that html2pdf.js uses html2canvas and jsPDF as dependencies, so it's a good idea to check each of those repositories' issue trackers to see if your problem has already been addressed.

Known issues

Rendering: The rendering engine html2canvas isn't perfect (though it's pretty good!). If html2canvas isn't rendering your content correctly, I can't fix it.

  • You can test this with something like this fiddle, to see if there's a problem in the canvas creation itself.

Node cloning (CSS etc): The way html2pdf.js clones your content before sending to html2canvas is buggy. A fix is currently being developed - try out:

Resizing: Currently, html2pdf.js resizes the root element to fit onto a PDF page (causing internal content to "reflow").

  • This is often desired behaviour, but not always.
  • There are plans to add alternate behaviour (e.g. "shrink-to-page"), but nothing that's ready to test yet.
  • Related project: Feature: Single-page PDFs

Rendered as image: html2pdf.js renders all content into an image, then places that image into a PDF.

  • This means text is not selectable or searchable, and causes large file sizes.
  • This is currently unavoidable, however recent improvements in jsPDF mean that it may soon be possible to render straight into vector graphics.
  • Related project: Feature: New renderer

Promise clashes: html2pdf.js relies on specific Promise behaviour, and can fail when used with custom Promise libraries.

Maximum size: HTML5 canvases have a maximum height/width. Anything larger will fail to render.

  • This is a limitation of HTML5 itself, and results in large PDFs rendering completely blank in html2pdf.js.
  • The jsPDF canvas renderer (mentioned in Known Issue #4) may be able to fix this issue!
  • Related project: Bugfix: Maximum canvas size

Tests

html2pdf.js is currently sorely lacking in unit tests. Any contributions or suggestions of automated (or manual) tests are welcome. This is high on the to-do list for this project.

Pull requests

If you want to create a new feature or bugfix, please feel free to fork and submit a pull request! Create a fork, branch off of master, and make changes to the /src/ files (rather than directly to /dist/). You can test your changes by rebuilding with npm run build.


Author: eKoopmans
Source Code: https://github.com/eKoopmans/html2pdf.js
License: MIT License

#html #pdf #javascrip 

Html2pdf.js: Client-side HTML-to-PDF Rendering using Pure JS

Building 5 Projects with HTML, CSS & Javascript (Part 2/2)

Building 5 Projects using HTML, CSS & Javascript (Part 1/2). Don't miss it because it's extremely useful and free.

Part 1:  https://morioh.com/p/ab6391eb662b?

#html  #css  #javascrip 

Building 5 Projects with HTML, CSS & Javascript (Part 2/2)
Florida  Feeney

Florida Feeney

1646834417

Build Responsive Personal Portfolio Website using HTML CSS & JS

Responsive Personal Portfolio Website using HTML, CSS And JAVASCRIPT | Personal Portfolio Website | Neumorphism Personal Portfolio Website Using Html Css javascript
👇source code :
https://github.com/animationbro/neumorphism_website-02.git

#html #css #javascrip 

Build Responsive Personal Portfolio Website using HTML CSS & JS
ADELA DAVID

ADELA DAVID

1646748000

Crypto js: JavaScript Library Of Crypto Standards

crypto-js 

JavaScript library of crypto standards.

Node.js (Install)

Requirements:

  • Node.js
  • npm (Node.js package manager)
npm install crypto-js

Usage

ES6 import for typical API call signing use case:

import sha256 from 'crypto-js/sha256';
import hmacSHA512 from 'crypto-js/hmac-sha512';
import Base64 from 'crypto-js/enc-base64';

const message, nonce, path, privateKey; // ...
const hashDigest = sha256(nonce + message);
const hmacDigest = Base64.stringify(hmacSHA512(path + hashDigest, privateKey));

Modular include:

var AES = require("crypto-js/aes");
var SHA256 = require("crypto-js/sha256");
...
console.log(SHA256("Message"));

Including all libraries, for access to extra methods:

var CryptoJS = require("crypto-js");
console.log(CryptoJS.HmacSHA1("Message", "Key"));

Client (browser)

Requirements:

  • Node.js
  • Bower (package manager for frontend)
bower install crypto-js

Usage

Modular include:

require.config({
    packages: [
        {
            name: 'crypto-js',
            location: 'path-to/bower_components/crypto-js',
            main: 'index'
        }
    ]
});

require(["crypto-js/aes", "crypto-js/sha256"], function (AES, SHA256) {
    console.log(SHA256("Message"));
});

Including all libraries, for access to extra methods:

// Above-mentioned will work or use this simple form
require.config({
    paths: {
        'crypto-js': 'path-to/bower_components/crypto-js/crypto-js'
    }
});

require(["crypto-js"], function (CryptoJS) {
    console.log(CryptoJS.HmacSHA1("Message", "Key"));
});

Usage without RequireJS

<script type="text/javascript" src="path-to/bower_components/crypto-js/crypto-js.js"></script>
<script type="text/javascript">
    var encrypted = CryptoJS.AES(...);
    var encrypted = CryptoJS.SHA256(...);
</script>

API

See: https://cryptojs.gitbook.io/docs/

AES Encryption

Plain text encryption

var CryptoJS = require("crypto-js");

// Encrypt
var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();

// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
var originalText = bytes.toString(CryptoJS.enc.Utf8);

console.log(originalText); // 'my message'

Object encryption

var CryptoJS = require("crypto-js");

var data = [{id: 1}, {id: 2}]

// Encrypt
var ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret key 123').toString();

// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
var decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));

console.log(decryptedData); // [{id: 1}, {id: 2}]

List of modules

  • crypto-js/core
  • crypto-js/x64-core
  • crypto-js/lib-typedarrays

  • crypto-js/md5
  • crypto-js/sha1
  • crypto-js/sha256
  • crypto-js/sha224
  • crypto-js/sha512
  • crypto-js/sha384
  • crypto-js/sha3
  • crypto-js/ripemd160

  • crypto-js/hmac-md5
  • crypto-js/hmac-sha1
  • crypto-js/hmac-sha256
  • crypto-js/hmac-sha224
  • crypto-js/hmac-sha512
  • crypto-js/hmac-sha384
  • crypto-js/hmac-sha3
  • crypto-js/hmac-ripemd160

  • crypto-js/pbkdf2

  • crypto-js/aes
  • crypto-js/tripledes
  • crypto-js/rc4
  • crypto-js/rabbit
  • crypto-js/rabbit-legacy
  • crypto-js/evpkdf

  • crypto-js/format-openssl
  • crypto-js/format-hex

  • crypto-js/enc-latin1
  • crypto-js/enc-utf8
  • crypto-js/enc-hex
  • crypto-js/enc-utf16
  • crypto-js/enc-base64

  • crypto-js/mode-cfb
  • crypto-js/mode-ctr
  • crypto-js/mode-ctr-gladman
  • crypto-js/mode-ofb
  • crypto-js/mode-ecb

  • crypto-js/pad-pkcs7
  • crypto-js/pad-ansix923
  • crypto-js/pad-iso10126
  • crypto-js/pad-iso97971
  • crypto-js/pad-zeropadding
  • crypto-js/pad-nopadding

Release notes

4.1.1

Fix module order in bundled release.

Include the browser field in the released package.json.

4.1.0

Added url safe variant of base64 encoding. 357

Avoid webpack to add crypto-browser package. 364

4.0.0

This is an update including breaking changes for some environments.

In this version Math.random() has been replaced by the random methods of the native crypto module.

For this reason CryptoJS might not run in some JavaScript environments without native crypto module. Such as IE 10 or before or React Native.

3.3.0

Rollback, 3.3.0 is the same as 3.1.9-1.

The move of using native secure crypto module will be shifted to a new 4.x.x version. As it is a breaking change the impact is too big for a minor release.

3.2.1

The usage of the native crypto module has been fixed. The import and access of the native crypto module has been improved.

3.2.0

In this version Math.random() has been replaced by the random methods of the native crypto module.

For this reason CryptoJS might does not run in some JavaScript environments without native crypto module. Such as IE 10 or before.

If it's absolute required to run CryptoJS in such an environment, stay with 3.1.x version. Encrypting and decrypting stays compatible. But keep in mind 3.1.x versions still use Math.random() which is cryptographically not secure, as it's not random enough.

This version came along with CRITICAL BUG.

DO NOT USE THIS VERSION! Please, go for a newer version!

3.1.x

The 3.1.x are based on the original CryptoJS, wrapped in CommonJS modules.


Author: brix
Source Code: https://github.com/brix/crypto-js
License: View license

#crypto #javascrip 

Crypto js: JavaScript Library Of Crypto Standards
Keith  Evans

Keith Evans

1646452800

Swagger UI Is A Collection Of HTML, JavaScript and CSS Assets

Introduction

Swagger UI allows anyone — be it your development team or your end consumers — to visualize and interact with the API’s resources without having any of the implementation logic in place. It’s automatically generated from your OpenAPI (formerly known as Swagger) Specification, with the visual documentation making it easy for back end implementation and client side consumption.

General

👉🏼 Want to score an easy open-source contribution? Check out our Good first issue label.

🕰️ Looking for the older version of Swagger UI? Refer to the 2.x branch.

This repository publishes three different NPM modules:

  • swagger-ui is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
  • swagger-ui-dist is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
  • swagger-ui-react is Swagger UI packaged as a React component for use in React applications.

We strongly suggest that you use swagger-ui instead of swagger-ui-dist if you're building a single-page application, since swagger-ui-dist is significantly larger.

If you are looking for plain ol' HTML/JS/CSS, download the latest release and copy the contents of the /dist folder to your server.

Compatibility

The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between Swagger UI and the OpenAPI Specification is as follows:

Swagger UI VersionRelease DateOpenAPI Spec compatibilityNotes
4.0.02021-11-032.0, 3.0tag v4.0.0
3.18.32018-08-032.0, 3.0tag v3.18.3
3.0.212017-07-262.0tag v3.0.21
2.2.102017-01-041.1, 1.2, 2.0tag v2.2.10
2.1.52016-07-201.1, 1.2, 2.0tag v2.1.5
2.0.242014-09-121.1, 1.2tag v2.0.24
1.0.132013-03-081.1, 1.2tag v1.0.13
1.0.12011-10-111.0, 1.1tag v1.0.1

Documentation

Usage

Customization

Development

Contributing

Integration Tests

You will need JDK of version 7 or higher as instructed here https://nightwatchjs.org/guide/getting-started/installation.html#install-selenium-server

Integration tests can be run locally with npm run e2e - be sure you aren't running a dev server when testing!

Browser support

Swagger UI works in the latest versions of Chrome, Safari, Firefox, and Edge.

Known Issues

To help with the migration, here are the currently known issues with 3.X. This list will update regularly, and will not include features that were not implemented in previous versions.

  • Only part of the parameters previously supported are available.
  • The JSON Form Editor is not implemented.
  • Support for collectionFormat is partial.
  • l10n (translations) is not implemented.
  • Relative path support for external files is not implemented.

Security contact

Please disclose any security-related issues or vulnerabilities by emailing security@swagger.io, instead of using the public issue tracker.


Author: swagger-api
Source Code: https://github.com/swagger-api/swagger-ui
License: Apache-2.0 License

#python #html #css #javascrip 

Swagger UI Is A Collection Of HTML, JavaScript and CSS Assets
曾 俊

曾 俊

1642808640

NodeJS NPM 入门 - Modules 模块管理器

NodeJS NPM 入门介绍 (什么是NPM?) 包管理工具器 模块管理器| Intro to NodeJS Node Package Manager (What is NPM?)

这是NodeJS后端编程系列的第六集,前几集,我和同学们分享了几个入门NodeJS后端编程的教学,相信现在同学们对NodeJS要怎么处理来自客户端的GET和POST请求相当熟悉了。

今天的教学,我会和你分享一个在NodeJS后端编程里非常重要的工具,那就是NPM,NPM是Node Package Manager的缩写,从字面上来看,它是NodeJS 包管理工具,也可以叫模块管理工具,但事实上,NPM所涵盖的功能不仅仅是一个模块管理工具,它还有两个极其重要的功能。

马上观看视频了解更多关于NPM的操作!

#nodejs #javascrip 

 

NodeJS NPM 入门 - Modules 模块管理器
Jane  Reid

Jane Reid

1642161600

Experiment Tracking For Machine And Deep Learning Projects

ModelChimp

What is ModelChimp?

ModelChimp is an experiment tracker for Deep Learning and Machine Learning experiments.

ModelChimp provides the following features:

  • Real-time tracking of parameters and metrics
  • Realtime charts for experiment metrics at epoch level
  • Code used for the experiment
  • Experiment comparison
  • Collaborate and share experiments with team members
  • Python objects storage such as data objects and model objects which can be used pulled for other experiments
  • Storage of test and validation images for computer vision use cases. Useful for post experiment forensics of deep learning models
  • Server based solution with user registration and authentication

Why ModelChimp?

The idea for ModelChimp came up when I was building a recommendation algorithm for a large retail company based in India. Along with my 6 member team, we would store the meta information related to each experiment in an excel sheet. Two of the biggest problems we encountered while using this approach were:

  1. Sometimes, we would miss out on logging the details while fine-tuning and analysing the model
  2. Sharing these excel sheets over email amongst the team members and the client was a cumbersome process

ModelChimp is a solution to this problem faced by data scientists and machine learning engineers/enthusiasts. They can spend more time on experiments and not on managing the data related to the experiments.

Installation

Choose either Docker based installation or the manual approach.

  • Docker
  • Production Deployment

Docker

  1. Docker is a prerequisite. You can download it from here - https://docs.docker.com/install/
$ git clone https://github.com/ModelChimp/modelchimp
$ cd modelchimp
$ bash docker.sh

After starting ModelChimp server, you can access it at http://localhost:8000

Use the following credentials to log in

username: admin@modelchimp.com
password: modelchimp123
  1. (Optional) If you are using modelchimp on a remote server then add the hostname or ip address in the .env file for the following variables
DOMAIN=<hostname/ip>
ALLOWED_HOSTS=.localhost,127.0.0.1,<hostname/ip>
  1. (Optional) For inviting team members, email credentials have to be added for the following variables in .env file
EMAIL_HOST=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_PORT=587
DEFAULT_FROM_EMAIL="noreply@modelchimp.com"

Production Deployment

  1. Modelchimp can be deployed referring the docker-compose.local.yml with the container orchestration of your choice. If you are not using any container orchestration and want to start it manually then you can use the following command
docker-compose -f docker-compose.local.yml up --build -d

This will start the containers in daemon mode on the machine where Modelchimp resides. Modelchimp can be accessed from port 8000

  1. (Optional) To store the data in an external postgres database. Add the following credentials to the .env file
DB_HOST=<DB_HOST>
DB_NAME=<DB_NAME>
DB_USER=<DB_USER>
DB_PASSWORD=<DB_PASSWORD>
DBPORT=
  1. (Optional) To store the file assets in an s3 bucket. Add the following credentials to the .env file
AWS_STORAGE_FLAG=True
AWS_ACCESS_KEY_ID=<ID>
AWS_SECRET_ACCESS_KEY=<KEY>
AWS_STORAGE_BUCKET_NAME=<bucket_name>
  1. (Optional) To invite team members to a project. Add the following email credentials to the .env file
EMAIL_HOST=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_PORT=587
DEFAULT_FROM_EMAIL="noreply@modelchimp.com"

Documentation


Author: ModelChimp
Source Code: https://github.com/ModelChimp/modelchimp
License: BSD-2-Clause License

#docker #python #machine-learning #deep-learning #javascrip #css #html #data-science 

Experiment Tracking For Machine And Deep Learning Projects