Node.js에서 콜백을 프라 미스로 대체


94

데이터베이스에 연결하고 데이터를 수신하는 여러 기능이있는 간단한 노드 모듈이 있습니다 (예 :이 기능).


dbConnection.js :

import mysql from 'mysql';

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'db'
});

export default {
  getUsers(callback) {
    connection.connect(() => {
      connection.query('SELECT * FROM Users', (err, result) => {
        if (!err){
          callback(result);
        }
      });
    });
  }
};

모듈은 다른 노드 모듈에서 다음과 같이 호출됩니다.


app.js :

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

데이터를 반환하기 위해 콜백 대신 promise를 사용하고 싶습니다. 지금까지 다음 스레드에서 중첩 된 약속에 대해 읽었습니다 : Writing Clean Code With Nested Promises ,하지만이 사용 사례에 대해 충분히 간단한 솔루션을 찾을 수 없습니다. result약속을 사용하여 반환하는 올바른 방법은 무엇입니까 ?


1
kriskowal의 Q 라이브러리를 사용하는 경우 Adapting Node를 참조하십시오 .
Bertrand Marron 2015

1
의 중복 가능성 내가 약속을 기존 콜백 API를 변환하려면 어떻게합니까? 질문을 더 구체적으로 작성하거나 종료하겠습니다
Bergi

@ leo.249 : Q 문서를 읽었습니까? 이미 코드에 적용하려고 하셨나요? 그렇다면 시도를 게시하세요 (작동하지 않더라도)? 정확히 어디에 갇혀 있습니까? 단순하지 않은 해결책을 찾은 것 같습니다. 게시 해주세요.
Bergi

3
@ leo.249 Q는 거의 유지되지 않습니다. 마지막 커밋은 3 개월 전이었습니다. v2 브랜치 만이 Q 개발자에게 흥미롭고 어쨌든 프로덕션 준비가 거의되지 않았습니다. 10 월부터 이슈 트래커에 댓글이없는 해결되지 않은 이슈가 있습니다. 잘 관리 된 promise 라이브러리를 고려할 것을 강력히 제안합니다.
Benjamin Gruenbaum

2
Super related 콜백 API를
프라 미스

답변:


102

사용 Promise수업

MDN의 Promise 문서를 살펴 보는 것이 좋습니다. 사용을위한 좋은 시작점을 제공하는 를 . 또는 온라인에서 사용할 수있는 많은 자습서가 있다고 확신합니다. :)

참고 : 최신 브라우저는 이미 ECMAScript 6 Promises 사양을 지원하며 (위에 링크 된 MDN 문서 참조) 타사 라이브러리없이 기본 구현을 사용하고자한다고 가정합니다.

실제 예는 ...

기본 원리는 다음과 같습니다.

  1. API가 호출되었습니다.
  2. 새로운 Promise 객체를 생성합니다.이 객체는 생성자 매개 변수로 단일 함수를 사용합니다.
  3. 귀하의 제공 기능이 기본이되는 구현에 의해 호출되며, 함수는 두 가지 기능을 부여 - resolvereject
  4. 논리를 수행 한 후에는 이들 중 하나를 호출하여 약속을 채우거나 오류로 거부합니다.

이것은 많은 것처럼 보일 수 있으므로 여기에 실제 예가 있습니다.

exports.getUsers = function getUsers () {
  // Return the Promise right away, unless you really need to
  // do something before you create a new Promise, but usually
  // this can go into the function below
  return new Promise((resolve, reject) => {
    // reject and resolve are functions provided by the Promise
    // implementation. Call only one of them.

    // Do your logic here - you can do WTF you want.:)
    connection.query('SELECT * FROM Users', (err, result) => {
      // PS. Fail fast! Handle errors first, then move to the
      // important stuff (that's a good practice at least)
      if (err) {
        // Reject the Promise with an error
        return reject(err)
      }

      // Resolve (or fulfill) the promise with data
      return resolve(result)
    })
  })
}

// Usage:
exports.getUsers()  // Returns a Promise!
  .then(users => {
    // Do stuff with users
  })
  .catch(err => {
    // handle errors
  })

async / await 언어 기능 사용 (Node.js> = 7.6)

Node.js 7.6에서 v8 JavaScript 컴파일러는 async / await 지원 으로 업그레이드되었습니다 . 이제 함수를로 선언 할 수 있습니다. asyncPromise, 비동기 함수가 실행을 완료하면 해결되는 a 를 자동으로 반환합니다 . 이 함수 내에서 await키워드를 사용하여 다른 Promise가 해결 될 때까지 기다릴 수 있습니다 .

다음은 예입니다.

exports.getUsers = async function getUsers() {
  // We are in an async function - this will return Promise
  // no matter what.

  // We can interact with other functions which return a
  // Promise very easily:
  const result = await connection.query('select * from users')

  // Interacting with callback-based APIs is a bit more
  // complicated but still very easy:
  const result2 = await new Promise((resolve, reject) => {
    connection.query('select * from users', (err, res) => {
      return void err ? reject(err) : resolve(res)
    })
  })
  // Returning a value will cause the promise to be resolved
  // with that value
  return result
}

14
Promises는 ECMAScript 2015 사양의 일부이며 Node v0.12에서 사용되는 v8은 사양의이 부분을 구현합니다. 그렇습니다. 그들은 노드 코어의 일부가 아닙니다. 언어의 일부입니다.
Robert Rossmann jul.

1
알다시피, Promises를 사용하려면 npm 패키지를 설치하고 require ()를 사용해야한다는 인상을 받았습니다. 나는 베어 본 / A ++ 스타일을 구현하는 npm에서 promise 패키지를 발견했으며이를 사용했지만 여전히 노드 자체 (JavaScript가 아님)에 익숙하지 않습니다.
macguru2000 jul.

이것은 약속을 작성하고 비동기 코드를 설계하는 가장 좋은 방법입니다. 주로 일관된 패턴이고 쉽게 읽을 수 있으며 고도로 구조화 된 코드를 허용하기 때문입니다.

31

함께 블루 버드 당신이 사용할 수있는 Promise.promisifyAll(그리고 Promise.promisify모든 개체에 약속 준비 방법을 추가).

var Promise = require('bluebird');
// Somewhere around here, the following line is called
Promise.promisifyAll(connection);

exports.getUsersAsync = function () {
    return connection.connectAsync()
        .then(function () {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

다음과 같이 사용하십시오.

getUsersAsync().then(console.log);

또는

// Spread because MySQL queries actually return two resulting arguments, 
// which Bluebird resolves as an array.
getUsersAsync().spread(function(rows, fields) {
    // Do whatever you want with either rows or fields.
});

처리기 추가

블루 버드 지원 기능이 많이, 그 중 하나는 그것의 도움으로 끝난 후 안전하게 연결을 처리 할 수 있습니다, 음식물 쓰레기 처리기입니다 Promise.usingPromise.prototype.disposer. 내 앱의 예는 다음과 같습니다.

function getConnection(host, user, password, port) {
    // connection was already promisified at this point

    // The object literal syntax is ES6, it's the equivalent of
    // {host: host, user: user, ... }
    var connection = mysql.createConnection({host, user, password, port});
    return connection.connectAsync()
        // connect callback doesn't have arguments. return connection.
        .return(connection) 
        .disposer(function(connection, promise) { 
            //Disposer is used when Promise.using is finished.
            connection.end();
        });
}

그런 다음 다음과 같이 사용하십시오.

exports.getUsersAsync = function () {
    return Promise.using(getConnection()).then(function (connection) {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

약속이 값으로 해결되면 (또는으로 거부 Error) 연결이 자동으로 종료됩니다 .


3
정답입니다. 덕분에 Q 대신 블루 버드를 사용하게되었습니다. 감사합니다!
Lior Erez

2
약속을 사용 try-catch하면 각 통화 에 사용 하는 데 동의하게됩니다 . 따라서이 작업을 자주 수행하고 코드 복잡성이 샘플과 유사하다면이를 재고해야합니다.
Andrey Popov

14

Node.js 버전 8.0.0 이상 :

더 이상 노드 API 메서드를 약속 하기 위해 bluebird 를 사용할 필요가 없습니다 . 버전 8 이상에서는 기본 util.promisify를 사용할 수 있기 때문입니다 .

const util = require('util');

const connectAsync = util.promisify(connection.connectAsync);
const queryAsync = util.promisify(connection.queryAsync);

exports.getUsersAsync = function () {
    return connectAsync()
        .then(function () {
            return queryAsync('SELECT * FROM Users')
        });
};

이제 약속을 수행하기 위해 타사 라이브러리를 사용할 필요가 없습니다.


3

데이터베이스 어댑터 API가 Promises자체적으로 출력하지 않는다고 가정하면 다음과 같이 할 수 있습니다.

exports.getUsers = function () {
    var promise;
    promise = new Promise();
    connection.connect(function () {
        connection.query('SELECT * FROM Users', function (err, result) {
            if(!err){
                promise.resolve(result);
            } else {
                promise.reject(err);
            }
        });
    });
    return promise.promise();
};

데이터베이스 API가 지원하는 경우 Promises다음과 같은 작업을 수행 할 수 있습니다. (여기서 Promises의 힘을 확인하면 콜백 플러 프가 거의 사라집니다.)

exports.getUsers = function () {
    return connection.connect().then(function () {
        return connection.query('SELECT * FROM Users');
    });
};

.then()새로운 (중첩 된) promise를 반환하는 데 사용 합니다.

전화 :

module.getUsers().done(function (result) { /* your code here */ });

내 약속에 목업 API를 사용했는데 API가 다를 수 있습니다. API를 보여 주시면 맞춤 설정할 수 있습니다.


2
Promise생성자와 .promise()메서드 가있는 Promise 라이브러리는 무엇입니까 ?
Bergi

감사합니다. 나는 단지 node.js를 연습하고 있는데 내가 올린 것은 약속을 사용하는 방법을 알아내는 아주 간단한 예제였다. 귀하의 솔루션은 좋아 보이지만 사용하려면 어떤 npm 패키지를 설치해야 promise = new Promise();합니까?
Lior Erez

API가 이제 Promise를 반환하지만, 당신은 운명의 피라미드를 제거하지 않았거나 콜백을 대체하기 위해 Promise가 작동하는 방식에 대한 예제를 만들지 않았습니다.
Madara의 유령

@ leo.249 잘 모르겠습니다. Promise / A +를 준수하는 Promise 라이브러리는 좋을 것입니다. 참조 : promisesaplus.com/@Bergi 관련이 없습니다. 인터페이스하는 API가 지원하지 않는 경우 @SecondRikudo Promises는 콜백을 사용하는 것입니다. 약속 영역에 들어가면 '피라미드'가 사라집니다. 작동 방식에 대한 두 번째 코드 예제를 참조하십시오.
Halcyon

@Halcyon 내 대답을 참조하십시오. 콜백을 사용하는 기존 API조차도 Promise ready API로 "약속"될 수 있으며 결과적으로 훨씬 더 깔끔한 코드가 생성됩니다.
Madara의 유령

3

2019 :

해당 네이티브 모듈 const {promisify} = require('util');을 사용 하여 평범한 이전 콜백 패턴을 약속 패턴으로 변환하여 async/await코드 에서 이점을 얻을 수 있습니다.

const {promisify} = require('util');
const glob = promisify(require('glob'));

app.get('/', async function (req, res) {
    const files = await glob('src/**/*-spec.js');
    res.render('mocha-template-test', {files});
});

2

promise를 설정할 때 두 개의 매개 변수 resolvereject. 성공의 경우 resolve결과로 호출 reject하고 실패한 경우 오류로 호출 합니다.

그런 다음 다음과 같이 작성할 수 있습니다.

getUsers().then(callback)

callback에서 반환 약속의 결과로 호출됩니다 getUsers즉,result


2

예를 들어 Q 라이브러리 사용 :

function getUsers(param){
    var d = Q.defer();

    connection.connect(function () {
    connection.query('SELECT * FROM Users', function (err, result) {
        if(!err){
            d.resolve(result);
        }
    });
    });
    return d.promise;   
}

1
그렇지 않으면 {d.reject (new Error (err)); }, 수정 하시겠습니까?
Russell

0

아래 코드는 노드 -v> 8.x에서만 작동합니다.

Promisified MySQL 미들웨어를 Node.js에 사용합니다.

이 기사 읽기 Node.js 8 및 Async / Await를 사용하여 MySQL 데이터베이스 미들웨어 생성

database.js

var mysql = require('mysql'); 

// node -v must > 8.x 
var util = require('util');


//  !!!!! for node version < 8.x only  !!!!!
// npm install util.promisify
//require('util.promisify').shim();
// -v < 8.x  has problem with async await so upgrade -v to v9.6.1 for this to work. 



// connection pool https://github.com/mysqljs/mysql   [1]
var pool = mysql.createPool({
  connectionLimit : process.env.mysql_connection_pool_Limit, // default:10
  host     : process.env.mysql_host,
  user     : process.env.mysql_user,
  password : process.env.mysql_password,
  database : process.env.mysql_database
})


// Ping database to check for common exception errors.
pool.getConnection((err, connection) => {
if (err) {
    if (err.code === 'PROTOCOL_CONNECTION_LOST') {
        console.error('Database connection was closed.')
    }
    if (err.code === 'ER_CON_COUNT_ERROR') {
        console.error('Database has too many connections.')
    }
    if (err.code === 'ECONNREFUSED') {
        console.error('Database connection was refused.')
    }
}

if (connection) connection.release()

 return
 })

// Promisify for Node.js async/await.
 pool.query = util.promisify(pool.query)



 module.exports = pool

노드 -v> 8.x를 업그레이드해야합니다.

await를 사용하려면 비동기 함수를 사용해야합니다.

예:

   var pool = require('./database')

  // node -v must > 8.x, --> async / await  
  router.get('/:template', async function(req, res, next) 
  {
      ...
    try {
         var _sql_rest_url = 'SELECT * FROM arcgis_viewer.rest_url WHERE id='+ _url_id;
         var rows = await pool.query(_sql_rest_url)

         _url  = rows[0].rest_url // first record, property name is 'rest_url'
         if (_center_lat   == null) {_center_lat = rows[0].center_lat  }
         if (_center_long  == null) {_center_long= rows[0].center_long }
         if (_center_zoom  == null) {_center_zoom= rows[0].center_zoom }          
         _place = rows[0].place


       } catch(err) {
                        throw new Error(err)
       }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.