Bagaimana cara mengonversi API panggilan balik yang ada ke janji?

721

Saya ingin bekerja dengan janji-janji tetapi saya memiliki API panggilan balik dalam format seperti:

1. Beban DOM atau acara satu kali lainnya:

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

};

2. Panggilan balik polos:

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

3. Callback gaya simpul ("nodeback"):

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

4. Seluruh perpustakaan dengan panggilan balik gaya simpul:

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

Bagaimana cara saya bekerja dengan API dalam janji-janji, bagaimana cara saya "menjanjikan" itu?

Benjamin Gruenbaum
sumber
Saya memposting jawaban saya sendiri tetapi jawaban yang berkembang tentang bagaimana melakukan ini untuk perpustakaan tertentu atau dalam lebih banyak keadaan dan pengeditan juga sangat disambut baik.
Benjamin Gruenbaum
@Bergi Itu ide yang menarik, saya mencoba membuat jawaban umum yang menggunakan dua pendekatan umum (Promise constructor dan deferred object). Saya mencoba memberikan dua alternatif jawaban. Saya setuju bahwa RTFMing menyelesaikan masalah ini tetapi kami sering mengalami masalah ini baik di sini maupun di pelacak bug, jadi saya pikir 'pertanyaan kanonik' sudah tersedia - Saya pikir RTFMing memecahkan sekitar 50% masalah di tag JS: D If Anda memiliki wawasan yang menarik untuk berkontribusi dalam jawaban atau mengeditnya akan sangat dihargai.
Benjamin Gruenbaum
Apakah membuat new Promisepenambahan overhead signifikan? Saya ingin membungkus semua fungsi Noje.js sinkron saya dalam Janji sehingga dapat menghapus semua kode sinkron dari aplikasi Node saya, tetapi apakah ini praktik terbaik? Dengan kata lain, fungsi yang menerima argumen statis (misalnya string) dan mengembalikan hasil yang dihitung, haruskah saya membungkusnya dengan janji? ... Saya membaca di suatu tempat bahwa Anda seharusnya tidak memiliki kode sinkron di Nodejs.
Ronnie Royston
1
@RonRoyston tidak, itu bukan ide yang baik untuk membungkus panggilan sinkron dengan janji - hanya panggilan tidak sinkron yang dapat melakukan I / O
Benjamin Gruenbaum

Jawaban:

744

Janji memiliki status, mereka mulai tertunda dan dapat puas dengan:

  • memenuhi artinya bahwa perhitungan selesai dengan sukses.
  • ditolak artinya penghitungan gagal.

Fungsi janji pengembalian tidak boleh dibuang , mereka harus mengembalikan penolakan sebagai gantinya. Melempar dari fungsi pengembalian janji akan memaksa Anda untuk menggunakan a } catch { dan a .catch. Orang-orang yang menggunakan API yang dijanjikan tidak berharap untuk membuang janji. Jika Anda tidak yakin bagaimana API async bekerja di JS - silakan lihat jawaban ini terlebih dahulu.

1. Beban DOM atau acara satu kali lainnya:

Jadi, membuat janji umumnya berarti menentukan kapan mereka puas - itu berarti ketika mereka pindah ke fase yang dipenuhi atau ditolak untuk menunjukkan data tersedia (dan dapat diakses dengan .then).

Dengan implementasi janji modern yang mendukung Promisekonstruktor seperti ES6 asli menjanjikan:

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

Anda kemudian akan menggunakan janji yang dihasilkan seperti:

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

Dengan pustaka yang mendukung ditangguhkan (Mari gunakan $ q untuk contoh ini di sini, tetapi kami juga akan menggunakan jQuery nanti):

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

Atau dengan jQuery seperti API, mengaitkan peristiwa yang terjadi sekali:

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

2. Panggilan balik polos:

API ini agak umum karena ... panggilan balik umum di JS. Mari kita lihat kasus umum memiliki onSuccessdan onFail:

function getUserData(userId, onLoad, onFail) { 

Dengan implementasi janji modern yang mendukung Promisekonstruktor seperti ES6 asli menjanjikan:

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

Dengan pustaka yang mendukung ditangguhkan (Mari gunakan jQuery untuk contoh ini di sini, tetapi kami juga menggunakan $ q di atas):

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

jQuery juga menawarkan $.Deferred(fn)formulir, yang memiliki keuntungan memungkinkan kita untuk menulis ekspresi yang sangat mirip dengan new Promise(fn)formulir, sebagai berikut:

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

Catatan: Di sini kita mengeksploitasi fakta bahwa jQuery yang ditangguhkan resolvedan rejectmetode "dapat dilepas"; yaitu. mereka terikat pada instance dari jQuery.Deferred (). Tidak semua lib menawarkan fitur ini.

3. Callback gaya simpul ("nodeback"):

Callback gaya simpul (nodebacks) memiliki format tertentu di mana callback selalu menjadi argumen terakhir dan parameter pertamanya adalah kesalahan. Pertama mari kita promisikan secara manual:

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

Untuk:

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

Dengan ditangguhkan Anda dapat melakukan hal berikut (mari kita gunakan Q untuk contoh ini, meskipun Q sekarang mendukung sintaks baru yang harus Anda sukai ):

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;   
}

Secara umum, Anda tidak boleh terlalu banyak menjanjikan hal-hal secara manual, sebagian besar perpustakaan janji yang dirancang dengan mempertimbangkan Node serta janji bawaan di Node 8+ memiliki metode bawaan untuk menjanjikan serangan balik. Sebagai contoh

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

4. Seluruh perpustakaan dengan panggilan balik gaya simpul:

Tidak ada aturan emas di sini, Anda menjanjikannya satu per satu. Namun, beberapa implementasi janji memungkinkan Anda melakukan ini secara massal, misalnya di Bluebird, mengonversi API nodeback ke API janji sesederhana:

Promise.promisifyAll(API);

Atau dengan janji asli di 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}), {});

Catatan:

  • Tentu saja, ketika Anda berada di .thenpawang Anda tidak perlu menjanjikan sesuatu. Mengembalikan janji dari .thenpawang akan menyelesaikan atau menolak dengan nilai janji itu. Melempar dari .thenpawang juga merupakan praktik yang baik dan akan menolak janji - ini adalah janji keselamatan lemparan yang terkenal.
  • Dalam onloadkasus aktual , Anda harus menggunakan addEventListenerdaripada onX.
Benjamin Gruenbaum
sumber
Benjamin, saya menerima undangan Anda untuk mengedit dan menambahkan contoh jQuery lebih lanjut ke kasus 2. Perlu peer review sebelum muncul. Harap Anda menyukainya.
Roamer-1888
@ Roamer-1888 ditolak karena saya tidak melihat dan menerimanya tepat waktu. Untuk apa nilainya saya tidak berpikir penambahan itu terlalu relevan meskipun bermanfaat.
Benjamin Gruenbaum
2
Benjamin, apakah atau tidak resolve()dan reject()ditulis untuk dapat digunakan kembali, saya berani mengedit yang saya sarankan relevan karena menawarkan contoh jQuery dari formulir $.Deferred(fn), yang sebaliknya kurang. Jika hanya satu contoh jQuery yang disertakan, maka saya menyarankan bahwa itu harus dari formulir ini daripada var d = $.Deferred();dll karena orang harus didorong untuk menggunakan $.Deferred(fn)formulir yang sering diabaikan , ditambah, dalam jawaban seperti ini, itu menempatkan jQuery lebih setara dengan Lib yang menggunakan Pola Konstruktor Pengungkap .
Roamer-1888
Heh, untuk menjadi 100% adil saya tidak tahu jQuery membiarkan Anda melakukannya $.Deferred(fn), jika Anda mengeditnya sebagai ganti contoh yang ada dalam 15 menit berikutnya saya yakin saya dapat mencoba menyetujuinya tepat waktu :)
Benjamin Gruenbaum
7
Ini jawaban yang bagus. Anda mungkin ingin memperbaruinya dengan menyebutkan juga util.promisify, bahwa Node.js akan ditambahkan ke intinya mulai dari RC 8.0.0. Ini berfungsi tidak jauh berbeda dari Bluebird Promise.promisify, tetapi memiliki keuntungan karena tidak memerlukan dependensi tambahan, jika Anda hanya menginginkan Promise asli. Saya telah menulis posting blog tentang util.promisify untuk siapa saja yang ingin membaca lebih lanjut tentang topik ini.
Bruno
55

Hari ini, saya dapat menggunakan Promisedi Node.jssebagai metode Javascript polos.

Contoh sederhana dan mendasar untuk Promise(dengan cara KISS ):

Kode API Async Javascript Biasa :

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

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

    successCallback( number / divider )

}

Promise Kode API Javascript Async:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(Saya sarankan mengunjungi sumber yang indah ini )

Juga Promisedapat digunakan dengan bersama-sama async\awaitdalam ES7membuat menunggu aliran program untuk fullfiledhasil seperti berikut:

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

Penggunaan lain dengan kode yang sama dengan menggunakan .then()metode

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) })

Promisejuga dapat digunakan pada platform apa pun yang didasarkan pada Node.js seperti react-native.

Bonus : Metode hybrid
( Metode panggilan balik diasumsikan memiliki dua parameter sebagai kesalahan dan hasil)

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 )

     })

}

Metode di atas dapat merespons hasil untuk panggilan balik mode lama dan penggunaan Janji.

Semoga ini membantu.

efkan
sumber
3
Ini sepertinya tidak menunjukkan bagaimana menobatkan janji.
Dmitri Zaitsev
33

Sebelum mengonversi fungsi sebagai janji Di Node.JS

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)
})

Setelah Konversi

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)
})

Jika Anda perlu menangani beberapa permintaan

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)
});
Sivashanmugam Kannan
sumber
23

Saya tidak berpikir window.onloadsaran dari @Benjamin akan bekerja sepanjang waktu, karena tidak mendeteksi apakah ia dipanggil setelah memuat. Saya telah digigit oleh banyak kali. Ini adalah versi yang harus selalu berfungsi:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
Leo
sumber
1
bukankah penggunaan cabang "sudah lengkap" setTimeout(resolve, 0)(atau setImmediate, jika tersedia) untuk memastikan bahwa ia dipanggil secara tidak sinkron?
Alnitak
5
@Alnakak Memanggil secara resolvesinkron baik-baik saja. thenPenangan Promise dijamin oleh kerangka kerja yang disebut asinkron , terlepas dari apakah resolvedisebut sinkron.
Jeff Bowman
15

Node.js 8.0.0 mencakup util.promisify()API baru yang memungkinkan API gaya panggilan balik Node.js dibungkus dalam fungsi yang mengembalikan Janji. Contoh penggunaan util.promisify()ditunjukkan di bawah ini.

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

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

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

Lihat Dukungan yang ditingkatkan untuk Janji

Gian Marco Gherardi
sumber
2
Sudah ada dua jawaban yang menjelaskan hal ini, mengapa memposting yang ketiga?
Benjamin Gruenbaum
1
Hanya karena versi node tersebut sekarang dirilis, dan saya telah melaporkan deskripsi dan tautan fitur "resmi".
Gian Marco Gherardi
14

Dalam kandidat rilis untuk Node.js 8.0.0, ada utilitas baru, util.promisify(Saya telah menulis tentang util.promisify ), yang merangkum kapasitas menjanjikan fungsi apa pun.

Ini tidak jauh berbeda dari pendekatan yang disarankan dalam jawaban lain, tetapi memiliki keuntungan menjadi metode inti, dan tidak memerlukan dependensi tambahan.

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

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

Kemudian Anda memiliki readFilemetode yang mengembalikan asli Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
Bruno
sumber
1
Hei, saya (OP) sebenarnya menyarankan util.promisifydua kali (kembali pada tahun 2014 ketika pertanyaan ini ditulis, dan beberapa bulan yang lalu - yang saya dorong sebagai anggota inti Node dan merupakan versi saat ini yang kami miliki di Node). Karena belum tersedia untuk umum - saya belum menambahkannya ke jawaban ini. Kami akan sangat menghargai umpan balik penggunaan dan mengetahui beberapa kesulitan untuk memiliki dokumen yang lebih baik untuk rilis :)
Benjamin Gruenbaum
1
Selain itu, Anda mungkin ingin membahas bendera khusus untuk mendapat janji dengan util.promisifydi posting blog Anda :)
Benjamin Gruenbaum
@BenjaminGruenbaum Apakah maksud Anda fakta bahwa menggunakan util.promisify.customsimbol itu mungkin untuk menimpa hasil util.promisify? Sejujurnya ini adalah rindu yang disengaja, karena saya belum dapat menemukan kasus penggunaan yang berguna. Mungkin Anda bisa memberi saya beberapa input?
Bruno
1
Tentu, pertimbangkan API suka fs.existsatau API yang tidak mengikuti konvensi Node - burung bluebird Promise.promisify akan salah, tetapi util.promisifymembuatnya benar.
Benjamin Gruenbaum
7

Anda dapat menggunakan janji-janji asli JavaScript dengan Node JS.

Tautan kode Cloud 9 saya: 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
Apoorv
sumber
7

Dengan javaScript vanilla biasa, inilah solusi untuk menjanjikan panggilan balik 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);
});
daviddavis
sumber
6

Perpustakaan Q oleh kriskowal termasuk fungsi panggilan balik ke janji. Metode seperti ini:

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

dapat dikonversi dengan Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
Jason Loveman
sumber
1
Jawaban kanonik sudah menyebutkan Q.denodeify. Apakah kita perlu menekankan pembantu perpustakaan?
Bergi
3
Saya menemukan ini berguna sebagai google tentang promisifying di Q mengarah di sini
Ed Sykes
4

Ketika Anda memiliki beberapa fungsi yang menerima panggilan balik dan Anda ingin mereka mengembalikan janji, Anda dapat menggunakan fungsi ini untuk melakukan konversi.

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;
    }
}
pengguna1852503
sumber
4

Di bawah simpul v7.6 + yang telah dibangun dalam janji dan async:

// 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;

Cara Penggunaan:

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

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
Paul Spaulding
sumber
3

Dalam Node.js 8 Anda dapat menjanjikan metode objek dengan cepat menggunakan modul npm ini:

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

Ini menggunakan util.promisify dan Proxy sehingga objek Anda tetap tidak berubah. Memoisasi juga dilakukan dengan menggunakan WeakMaps). Berikut ini beberapa contohnya:

Dengan benda:

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

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

Dengan fungsi:

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

Anda bahkan dapat menggunakan asli calldan applyuntuk mengikat beberapa konteks:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
Lakukan Async
sumber
2

Anda dapat menggunakan Promise asli di ES6, untuk contoh berurusan dengan setTimeout:

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
    });

}

Dalam contoh ini, Janji tidak memiliki alasan untuk gagal, jadi reject()tidak pernah disebut.

Nicolas Zozol
sumber
2

The gaya callback fungsi selalu seperti ini (hampir semua fungsi dalam node.js gaya ini):

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

Gaya ini memiliki fitur yang sama:

  1. fungsi callback dilewatkan oleh argumen terakhir.

  2. fungsi callback selalu menerima objek kesalahan sebagai argumen pertama.

Jadi, Anda bisa menulis fungsi untuk mengonversi fungsi dengan gaya ini seperti ini:

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
        )
    )
)

Untuk lebih ringkas, contoh di atas digunakan ramda.js. Ramda.js adalah perpustakaan yang sangat baik untuk pemrograman fungsional. Dalam kode di atas, kami menggunakan itu berlaku (seperti javascript function.prototype.apply) dan menambahkan (seperti javascript function.prototype.push). Jadi, kita bisa mengonversi fungsi gaya panggilan balik ke fungsi gaya janji sekarang:

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

toPromise dan checkErr fungsi sendiri oleh mengamuk perpustakaan, itu adalah fungsional pemrograman perpustakaan fork oleh ramda.js (buat oleh saya).

Semoga jawaban ini bermanfaat bagi Anda.

jituanlin
sumber
2

Anda dapat melakukan sesuatu seperti ini

// @flow

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

export default toPromise

Lalu gunakan itu

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

  console.log(friends)
}
onmyway133
sumber
2
Hai, saya tidak yakin apa ini menambah jawaban yang ada (mungkin mengklarifikasi?). Juga, tidak perlu untuk mencoba / menangkap di dalam konstruktor janji (ini melakukan ini secara otomatis untuk Anda). Tidak jelas juga untuk apa fungsi ini berfungsi (yang memanggil panggilan balik dengan satu argumen tentang kesuksesan? Bagaimana kesalahan ditangani?)
Benjamin Gruenbaum
1

es6-promisify mengonversi fungsi berbasis panggilan balik ke fungsi berbasis Janji.

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

const promisedFn = promisify(callbackedFn, args);

Ref: https://www.npmjs.com/package/es6-promisify

Pujan Srivastava
sumber
1

Versi callbackfungsi saya yang dijanjikan adalah Pfungsi:

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))

The PFungsi mensyaratkan bahwa tanda tangan callback harus callback(error,result).

loretoparisi
sumber
1
Apa keuntungannya dibandingkan dengan yang dijanjikan atau dari jawaban di atas?
Benjamin Gruenbaum
Apa yang Anda maksud untuk promisify asli?
loretoparisi
util.promisify(fn)
Benjamin Gruenbaum
ah ya tentu saja :). Adil dan contoh untuk menunjukkan ide dasar. Bahkan Anda dapat melihat bagaimana bahkan yang asli mensyaratkan bahwa tanda tangan fungsi harus didefinisikan beberapa orang seperti (err, value) => ...atau Anda harus mendefinisikan yang kustom (lihat fungsi Custom promisified). Terima kasih catcha baik.
loretoparisi
1
@loretoparisi FYI, var P = function (fn, ...args) { return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); };akan melakukan hal yang sama seperti milik Anda dan itu jauh lebih sederhana.
Patrick Roberts
1

Di bawah ini adalah implementasi bagaimana suatu fungsi (API panggilan balik) dapat dikonversi menjadi janji.

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');
Mzndako
sumber
-2

Ini seperti 5 tahun terlambat, tapi saya ingin memposting di sini versi promesify saya yang mengambil fungsi dari callback API dan mengubahnya menjadi janji-janji

const promesify = fn => {
  return (...params) => ({
    then: cbThen => ({
      catch: cbCatch => {
        fn(...params, cbThen, cbCatch);
      }
    })
  });
};

Lihatlah versi yang sangat sederhana ini di sini: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

Julian Torregrosa
sumber
1
Itu bukan janji, itu tidak berantai, berurusan dengan kesalahan yang dilemparkan dalam panggilan balik atau menerima parameter kedua saat itu ...
Benjamin Gruenbaum