기존 콜백 API를 프라 미스로 어떻게 변환합니까?

Mar 20 2014

약속으로 작업하고 싶지만 다음과 같은 형식의 콜백 API가 있습니다.

1. DOM로드 또는 기타 일회성 이벤트 :

window.onload; // set to callback
...
window.onload = function() {

};

2. 일반 콜백 :

function request(onChangeHandler) {
    ...
}
request(function() {
    // change happened
    ...
});

3. 노드 스타일 콜백 ( "nodeback") :

function getStuff(dat, callback) {
    ...
}
getStuff("dataParam", function(err, data) {
    ...
})

4. 노드 스타일 콜백이있는 전체 라이브러리 :

API;
API.one(function(err, data) {
    API.two(function(err, data2) {
        API.three(function(err, data3) {
            ...
        });
    });
});

프라 미스에서 API로 작업하려면 어떻게해야합니까? "프로 미스"하는 방법은 무엇입니까?

답변

769 BenjaminGruenbaum Mar 20 2014 at 05:47

Promise에는 상태가 있으며 보류 중으로 시작하여 다음과 같이 해결할 수 있습니다.

  • 충족 됨은 계산이 성공적으로 완료 되었음을 의미합니다.
  • 거부 됨은 계산이 실패했음을 의미합니다.

Promise 반환 함수 는 절대 throw해서는 안되며 대신 거부를 반환해야합니다. promise 반환 함수에서 던지면 a } catch { a를 모두 사용해야합니다 .catch. 약속 된 API를 사용하는 사람들은 약속을 기대하지 않습니다. JS에서 비동기 API가 어떻게 작동하는지 확실하지 않은 경우 먼저이 답변을 참조하십시오 .

1. DOM로드 또는 기타 일회성 이벤트 :

따라서 프라 미스를 생성한다는 것은 일반적으로 정착시기를 지정하는 것을 의미합니다. 즉, 데이터를 사용할 수 있음을 나타 내기 위해 이행되거나 거부 된 단계로 이동하는시기를 의미합니다 (그리고로 액세스 할 수 있음 .then).

Promise네이티브 ES6 약속과 같은 생성자 를 지원하는 현대적인 약속 구현 :

function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}

그런 다음 결과 약속을 다음과 같이 사용합니다.

load().then(function() {
    // Do things after onload
});

지연을 지원하는 라이브러리 (여기서는이 예제에 $ q를 사용하지만 나중에 jQuery도 사용할 것임) :

function load() {
    var d = $q.defer();
    window.onload = function() { d.resolve(); };
    return d.promise;
}

또는 API와 같은 jQuery를 사용하여 한 번 발생하는 이벤트에 연결 :

function done() {
    var d = $.Deferred();
    $("#myObject").once("click",function() {
        d.resolve();
    });
    return d.promise();
}

2. 일반 콜백 :

이러한 API는 자바 스크립트에서 일반적으로 사용되는 콜백이기 때문에 일반적입니다. onSuccessand가 있는 일반적인 경우를 살펴 보겠습니다 onFail.

function getUserData(userId, onLoad, onFail) { …

Promise네이티브 ES6 약속과 같은 생성자 를 지원하는 현대적인 약속 구현 :

function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}

지연을 지원하는 라이브러리 사용 (여기서는이 예제에서는 jQuery를 사용하지만 위의 $ q도 사용했습니다) :

function getUserDataAsync(userId) {
    var d = $.Deferred();
    getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
    return d.promise();
}

jQuery는 또한 다음 과 같이 양식을 $.Deferred(fn)매우 가깝게 에뮬레이트하는 표현식을 작성할 수있는 장점이있는 new Promise(fn)양식을 제공합니다.

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}

참고 : 여기서 우리는 지연된 jQuery resolvereject메소드가 "분리 가능" 하다는 사실을 이용합니다 . 즉. 그들은 jQuery.Deferred () 의 인스턴스 에 바인딩됩니다 . 모든 libs가이 기능을 제공하는 것은 아닙니다.

3. 노드 스타일 콜백 ( "nodeback") :

노드 스타일 콜백 (노드 백)에는 콜백이 항상 마지막 인수이고 첫 번째 매개 변수가 오류 인 특정 형식이 있습니다. 먼저 수동으로 약속합시다.

getStuff("dataParam", function(err, data) { …

에:

function getStuffAsync(param) {
    return new Promise(function(resolve, reject) {
        getStuff(param, function(err, data) {
            if (err !== null) reject(err);
            else resolve(data);
        });
    });
}

지연을 사용하면 다음을 수행 할 수 있습니다 (이 예에서는 Q를 사용하지만 Q는 이제 선호해야하는 새 구문 지원함 ).

function getStuffAsync(param) {
    var d = Q.defer();
    getStuff(param, function(err, data) {
        if (err !== null) d.reject(err);
        else d.resolve(data);
    });
    return d.promise;   
}

일반적으로 수동으로 약속을 너무 많이하면 안됩니다. Node 8+의 기본 약속뿐 아니라 Node를 염두에두고 설계된 대부분의 약속 라이브러리에는 노드 백 약속을위한 기본 제공 방법이 있습니다. 예를 들면

var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only

4. 노드 스타일 콜백이있는 전체 라이브러리 :

여기에는 황금률이 ​​없습니다. 하나씩 약속합니다. 그러나 일부 promise 구현에서는이를 일괄 적으로 수행 할 수 있습니다. 예를 들어 Bluebird에서 nodeback API를 promise API로 변환하는 것은 다음과 같이 간단합니다.

Promise.promisifyAll(API);

또는 Node의 기본 약속 으로 :

const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)}))
                         .reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

메모:

  • 물론, 당신이 .then핸들러 안에있을 때 약속 할 필요가 없습니다. .then핸들러 에서 프라 미스를 반환하면 해당 프라 미스의 값으로 해결되거나 거부됩니다. .then핸들러 에서 던지는 것도 좋은 습관이며 약속을 거부합니다. 이것은 유명한 약속 던지기 안전입니다.
  • 실제에 onload경우에, 당신은 사용해야 addEventListener보다는 onX.
58 efkan Jan 02 2017 at 20:19

오늘은 Promisein Node.js을 일반 Javascript 메서드로 사용할 수 있습니다 .

Promise( KISS 방식으로)에 대한 간단하고 기본적인 예 :

일반 자바 스크립트 비동기 API 코드 :

function divisionAPI (number, divider, successCallback, errorCallback) {

    if (divider == 0) {
        return errorCallback( new Error("Division by zero") )
    }

    successCallback( number / divider )

}

Promise 자바 스크립트 비동기 API 코드 :

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            return rejected( new Error("Division by zero") )
        }

        fulfilled( number / divider )

     })

}

( 이 아름다운 소스를 방문 하는 것이 좋습니다 )

또한 in Promise과 함께 사용 하여 프로그램 흐름 이 다음과 같은 결과를 기다리도록 할 수 있습니다 .async\awaitES7fullfiled

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

        // wait 3000 milliseconds before calling fulfilled() method
        setTimeout ( 
            function() {
                fulfilled( name )
            }, 
            3000
        )

    })

}


async function foo () {

    var name = await getName(); // awaits for a fulfilled result!

    console.log(name); // the console writes "John Doe" after 3000 milliseconds

}


foo() // calling the foo() method to run the code

.then()방법 을 사용하여 동일한 코드로 다른 사용법

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

        // wait 3000 milliseconds before calling fulfilled() method
        setTimeout ( 
            function() {
                fulfilled( name )
            }, 
            3000
        )

    })

}


// the console writes "John Doe" after 3000 milliseconds
getName().then(function(name){ console.log(name) })

Promise.js와 같은 Node.js를 기반으로하는 모든 플랫폼에서 사용할 수도 있습니다 react-native.

보너스 : 하이브리드 메소드
(콜백 메소드는 오류와 결과로 두 개의 매개 변수가 있다고 가정)

function divisionAPI (number, divider, callback) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            let error = new Error("Division by zero")
            callback && callback( error )
            return rejected( error )
        }

        let result = number / divider
        callback && callback( null, result )
        fulfilled( result )

     })

}

위의 메서드는 구식 콜백 및 Promise 사용에 대한 결과를 응답 할 수 있습니다.

도움이 되었기를 바랍니다.

35 SivaKannan Aug 11 2017 at 18:31

Node.JS에서 함수를 promise로 변환하기 전에

var request = require('request'); //http wrapped module

function requestWrapper(url, callback) {
    request.get(url, function (err, response) {
      if (err) {
        callback(err);
      }else{
        callback(null, response);             
      }      
    })
}


requestWrapper(url, function (err, response) {
    console.log(err, response)
})

변환 후

var request = require('request');

function requestWrapper(url) {
  return new Promise(function (resolve, reject) { //returning promise
    request.get(url, function (err, response) {
      if (err) {
        reject(err); //promise reject
      }else{
        resolve(response); //promise resolve
      }
    })
  })
}


requestWrapper('http://localhost:8080/promise_request/1').then(function(response){
    console.log(response) //resolve callback(success)
}).catch(function(error){
    console.log(error) //reject callback(failure)
})

여러 요청을 처리해야하는 경우

var allRequests = [];
allRequests.push(requestWrapper('http://localhost:8080/promise_request/1')) 
allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))    

Promise.all(allRequests).then(function (results) {
  console.log(results);//result will be array which contains each promise response
}).catch(function (err) {
  console.log(err)
});
23 Leo Jan 14 2015 at 11:15

window.onload@Benjamin 의 제안은로드 후 호출되는지 여부를 감지하지 못하기 때문에 항상 작동 한다고 생각 하지 않습니다. 나는 여러 번 물렸다. 다음은 항상 작동해야하는 버전입니다.

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
15 GianMarco May 31 2017 at 13:46

Node.js 8.0.0에는 util.promisify()표준 Node.js 콜백 스타일 API를 Promise를 반환하는 함수로 래핑 할 수 있는 새로운 API가 포함 되어 있습니다. 의 사용 예가 util.promisify()아래에 나와 있습니다.

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

readFile('/some/file')
  .then((data) => { /** ... **/ })
  .catch((err) => { /** ... **/ });

Promise에 대한 향상된 지원을 참조하십시오.

14 Bruno May 16 2017 at 12:35

Node.js 8.0.0의 릴리스 후보에는 모든 기능을 약속하는 용량을 캡슐화 하는 새로운 유틸리티 util.promisify( util.promisify 에 대해 작성 했습니다 )가 있습니다.

다른 답변에서 제안한 접근 방식과 크게 다르지 않지만 핵심 방법이며 추가 종속성이 필요하지 않은 이점이 있습니다.

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

그런 다음 readFile네이티브 Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
7 Apoorv Jun 20 2016 at 20:38

Node JS와 함께 JavaScript 네이티브 프라 미스를 사용할 수 있습니다.

My Cloud 9 코드 링크 : https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
        request.get(url, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                resolve(body);
            }
            else {
                reject(error);
            }
        })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
    //get the post with post id 100
    promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
        var obj = JSON.parse(result);
        return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
    })
    .catch(function (e) {
        console.log(e);
    })
    .then(function (result) {
        res.end(result);
    })
})

var server = app.listen(8081, function () {
    var host = server.address().address
    var port = server.address().port

    console.log("Example app listening at http://%s:%s", host, port)
})

//run webservice on browser : http://localhost:8081/listAlbums
7 daviddavis Nov 28 2016 at 10:07

평범한 오래된 바닐라 자바 ​​스크립트를 사용하여 API 콜백을 약속하는 솔루션이 있습니다.

function get(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url);
        xhr.addEventListener('readystatechange', function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    console.log('successful ... should call callback ... ');
                    callback(null, JSON.parse(xhr.responseText));
                } else {
                    console.log('error ... callback with error data ... ');
                    callback(xhr, null);
                }
            }
        });
        xhr.send();
    }

/**
     * @function promisify: convert api based callbacks to promises
     * @description takes in a factory function and promisifies it
     * @params {function} input function to promisify
     * @params {array} an array of inputs to the function to be promisified
     * @return {function} promisified function
     * */
    function promisify(fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return new Promise(function(resolve, reject) {
                fn.apply(null, args.concat(function (err, result) {
                    if (err) reject(err);
                    else resolve(result);
                }));
            });
        }
    }

var get_promisified = promisify(get);
var promise = get_promisified('some_url');
promise.then(function (data) {
        // corresponds to the resolve function
        console.log('successful operation: ', data);
}, function (error) {
        console.log(error);
});
6 JasonLoveman Apr 08 2015 at 01:30

kriskowal의 Q 라이브러리에는 callback-to-promise 함수가 포함되어 있습니다. 다음과 같은 방법 :

obj.prototype.dosomething(params, cb) {
  ...blah blah...
  cb(error, results);
}

Q.ninvoke로 변환 가능

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
4 user1852503 Aug 04 2016 at 07:45

콜백을받는 함수가 몇 개 있고 프라 미스를 대신 반환하도록하려면이 함수를 사용하여 변환을 수행 할 수 있습니다.

function callbackToPromise(func){

    return function(){

        // change this to use what ever promise lib you are using
        // In this case i'm using angular $q that I exposed on a util module

        var defered = util.$q.defer();

        var cb = (val) => {
            defered.resolve(val);
        }

        var args = Array.prototype.slice.call(arguments);
        args.push(cb);    
        func.apply(this, args);

        return defered.promise;
    }
}
4 PaulSpaulding Apr 12 2017 at 23:48

프라 미스와 비동기가 내장 된 노드 v7.6 +에서 :

// promisify.js
let promisify = fn => (...args) =>
    new Promise((resolve, reject) =>
        fn(...args, (err, result) => {
            if (err) return reject(err);
            return resolve(result);
        })
    );

module.exports = promisify;

사용하는 방법:

let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
3 DoAsync Oct 13 2017 at 05:19

Node.js 8에서는 다음 npm 모듈을 사용 하여 즉시 객체 메서드 약속 할 수 있습니다 .

https://www.npmjs.com/package/doasync

util.promisify프록시를 사용 하여 개체가 변경되지 않도록합니다. MemoizationWeakMaps를 사용하여 수행됩니다.) 여기 예시들이 있습니다 :

개체 사용 :

const fs = require('fs');
const doAsync = require('doasync');

doAsync(fs).readFile('package.json', 'utf8')
  .then(result => {
    console.dir(JSON.parse(result), {colors: true});
  });

기능 포함 :

doAsync(request)('http://www.google.com')
  .then(({body}) => {
    console.log(body);
    // ...
  });

네이티브를 사용 call하고 apply일부 컨텍스트를 바인딩 할 수도 있습니다 .

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
2 NicolasZozol Jan 22 2017 at 20:22

setTimeout 처리의 예를 위해 ES6에서 네이티브 Promise 를 사용할 수 있습니다 .

enqueue(data) {

    const queue = this;
    // returns the Promise
    return new Promise(function (resolve, reject) {
        setTimeout(()=> {
                queue.source.push(data);
                resolve(queue); //call native resolve when finish
            }
            , 10); // resolve() will be called in 10 ms
    });

}

이 예제에서 Promise는 실패 할 이유가 없으므로 reject()절대 호출되지 않습니다.

2 jituanlin Jul 30 2017 at 20:39

콜백 스타일 항상이 같은 기능 (Node.js를 거의 모든 기능이 스타일) :

//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))

이 스타일에는 동일한 기능이 있습니다.

  1. 콜백 함수는 마지막 인수에 의해 전달됩니다.

  2. 콜백 함수는 항상 오류 객체를 첫 번째 인수로 받아들입니다.

따라서 다음과 같이이 스타일로 함수를 변환하는 함수를 작성할 수 있습니다.

const R =require('ramda')

/**
 * A convenient function for handle error in callback function.
 * Accept two function res(resolve) and rej(reject) ,
 * return a wrap function that accept a list arguments,
 * the first argument as error, if error is null,
 * the res function will call,else the rej function.
 * @param {function} res the function which will call when no error throw
 * @param {function} rej the function which will call when  error occur
 * @return {function} return a function that accept a list arguments,
 * the first argument as error, if error is null, the res function
 * will call,else the rej function
 **/
const checkErr = (res, rej) => (err, ...data) => R.ifElse(
    R.propEq('err', null),
    R.compose(
        res,
        R.prop('data')
    ),
    R.compose(
        rej,
        R.prop('err')
    )
)({err, data})

/**
 * wrap the callback style function to Promise style function,
 * the callback style function must restrict by convention:
 * 1. the function must put the callback function where the last of arguments,
 * such as (arg1,arg2,arg3,arg...,callback)
 * 2. the callback function must call as callback(err,arg1,arg2,arg...)
 * @param {function} fun the callback style function to transform
 * @return {function} return the new function that will return a Promise,
 * while the origin function throw a error, the Promise will be Promise.reject(error),
 * while the origin function work fine, the Promise will be Promise.resolve(args: array),
 * the args is which callback function accept
 * */
 const toPromise = (fun) => (...args) => new Promise(
    (res, rej) => R.apply(
        fun,
        R.append(
            checkErr(res, rej),
            args
        )
    )
)

더 간결하게 위의 예에서는 ramda.js를 사용했습니다. Ramda.js는 함수형 프로그래밍을위한 훌륭한 라이브러리입니다. 위의 코드에서는 apply (예 : javascript function.prototype.apply) 및 append (예 : javascript function.prototype.push)를 사용했습니다. 이제 콜백 스타일 함수를 promise 스타일 함수로 변환 할 수 있습니다.

const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
    .then(
        (files) => console.log(files),
        (err) => console.log(err)
    )

toPromisecheckErr 함수는 berserk 라이브러리 가 소유하고 , ramda.js 의 함수 프로그래밍 라이브러리 포크입니다 (내가 생성).

이 답변이 도움이 되었기를 바랍니다.

2 onmyway133 Oct 09 2018 at 20:35

다음과 같이 할 수 있습니다.

// @flow

const toPromise = (f: (any) => void) => {
  return new Promise<any>((resolve, reject) => {
    try {
      f((result) => {
        resolve(result)
      })
    } catch (e) {
      reject(e)
    }
  })
}

export default toPromise

그런 다음 사용

async loadData() {
  const friends = await toPromise(FriendsManager.loadFriends)

  console.log(friends)
}
1 Pujan Oct 18 2017 at 06:56

es6-promisify 콜백 기반 함수를 Promise 기반 함수로 변환합니다.

const promisify = require('es6-promisify');

const promisedFn = promisify(callbackedFn, args);

참고 : https://www.npmjs.com/package/es6-promisify

1 loretoparisi Nov 30 2017 at 06:34

내 promisify 버전의 callback함수는 다음과 P같습니다.

var P = function() {
  var self = this;
  var method = arguments[0];
  var params = Array.prototype.slice.call(arguments, 1);
  return new Promise((resolve, reject) => {
    if (method && typeof(method) == 'function') {
      params.push(function(err, state) {
        if (!err) return resolve(state)
        else return reject(err);
      });
      method.apply(self, params);
    } else return reject(new Error('not a function'));
  });
}
var callback = function(par, callback) {
  var rnd = Math.floor(Math.random() * 2) + 1;
  return rnd > 1 ? callback(null, par) : callback(new Error("trap"));
}

callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))

P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))

P함수를 사용하려면 콜백 서명이이어야합니다 callback(error,result).

1 Mzndako Jun 21 2019 at 17:38

다음은 함수 (콜백 API)를 프라 미스로 변환하는 방법을 구현 한 것입니다.

function promisify(functionToExec) {
  return function() {
    var array = Object.values(arguments);
    return new Promise((resolve, reject) => {
      array.push(resolve)
      try {
         functionToExec.apply(null, array);
      } catch (error) {
         reject(error)
      }
    })
  }
}

// USE SCENARIO

function apiFunction (path, callback) { // Not a promise
  // Logic
}

var promisedFunction = promisify(apiFunction);

promisedFunction('path').then(()=>{
  // Receive the result here (callback)
})

// Or use it with await like this
let result = await promisedFunction('path');

1 JosiahNyarega Sep 25 2020 at 01:28

미래에서 😄

내가 일반적으로 사용하는 간단한 일반 함수.

const promisify = (fn, ...args) => {
  return new Promise((resolve, reject) => {
    fn(...args, (err, data) => {
      if (err) {
        return reject(err);
      }
      resolve(data);
    });
  });
};

이것을 어떻게 사용 하는가

   promisify(fn, arg1, arg2)

이 답변을 찾고 있지는 않지만 사용 가능한 유틸리티의 내부 작동을 이해하는 데 도움이 될 것입니다.