Bagaimana cara mengonversi API panggilan balik yang ada menjadi promise?

Mar 20 2014

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

1. Pemuatan DOM atau kejadian satu kali lainnya:

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

};

2. Telepon balik biasa:

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

3. Callback gaya node ("nodeback"):

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

4. Seluruh pustaka dengan callback gaya node:

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

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

Jawaban

769 BenjaminGruenbaum Mar 20 2014 at 05:47

Janji memiliki status, mulai sebagai menunggu dan dapat diselesaikan dengan:

  • terpenuhi artinya komputasi berhasil diselesaikan.
  • ditolak artinya komputasi gagal.

Fungsi pengembalian janji tidak boleh dilemparkan , melainkan harus mengembalikan penolakan. Melempar dari fungsi pengembalian promise akan memaksa Anda menggunakan a } catch { dan a .catch. Orang yang menggunakan API yang dijanjikan tidak mengharapkan janji untuk dilempar. Jika Anda tidak yakin bagaimana async API bekerja di JS - harap lihat jawaban ini terlebih dahulu.

1. Pemuatan DOM atau kejadian satu kali lainnya:

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

Dengan implementasi promise modern yang mendukung Promisekonstruktor seperti promise ES6 asli:

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

Anda kemudian akan menggunakan janji yang dihasilkan seperti ini:

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 acara yang terjadi satu kali:

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

2. Telepon balik biasa:

API ini agak umum karena well… callback biasa terjadi di JS. Mari kita lihat kasus umum memiliki onSuccessdan onFail:

function getUserData(userId, onLoad, onFail) { …

Dengan implementasi promise modern yang mendukung Promisekonstruktor seperti promise ES6 asli:

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 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 deferred's resolveand rejectmethod adalah "dilepas"; yaitu. mereka terikat ke instance jQuery.Deferred (). Tidak semua libs menawarkan fitur ini.

3. Callback gaya node ("nodeback"):

Callback gaya node (nodebacks) memiliki format tertentu di mana callback selalu menjadi argumen terakhir dan parameter pertamanya adalah kesalahan. Mari pertama kita menjanjikan satu 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 deferreds Anda dapat melakukan hal berikut (mari gunakan Q untuk contoh ini, meskipun Q sekarang mendukung sintaks baru yang Anda pilih ):

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 sesuatu secara manual, sebagian besar library promise yang dirancang dengan mempertimbangkan Node serta promise asli di Node 8+ memiliki metode bawaan untuk menjanjikan nodeback. 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 pustaka dengan callback gaya node:

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

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:

  • Tentunya saat anda berada dalam .thenhandler anda tidak perlu menjanjikan banyak hal. 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 keselamatan melempar janji yang terkenal.
  • Dalam onloadkasus aktual , Anda harus menggunakan addEventListenerdaripada onX.
58 efkan Jan 02 2017 at 20:19

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

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

Kode API Asinkron Javascript Biasa :

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

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

    successCallback( number / divider )

}

Promise Kode Javascript Async API:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(Saya merekomendasikan mengunjungi sumber yang indah ini )

Juga Promisedapat digunakan dengan together async\awaitin ES7untuk membuat alur program menunggu 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 merespon hasil untuk penggunaan old fashion callback dan Promise.

Semoga ini membantu.

35 SivaKannan Aug 11 2017 at 18:31

Sebelum mengonversi fungsi sebagai promise dalam 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 Mengubahnya

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 banyak 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)
});
23 Leo Jan 14 2015 at 11:15

Saya rasa window.onloadsaran @Benjamin tidak akan berfungsi sepanjang waktu, karena tidak mendeteksi apakah dipanggil setelah pemuatan. Saya telah digigit berkali-kali. Ini adalah versi yang seharusnya selalu berfungsi:

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 menyertakan util.promisify()API baru yang memungkinkan API gaya panggilan balik Node.js standar dimasukkan ke dalam fungsi yang mengembalikan Promise. 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 Peningkatan dukungan untuk Promises

14 Bruno May 16 2017 at 12:35

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

Ini tidak jauh berbeda dengan pendekatan yang disarankan dalam jawaban lain, tetapi memiliki keunggulan sebagai metode inti, dan tidak memerlukan ketergantungan tambahan.

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

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

Maka Anda memiliki readFilemetode yang mengembalikan native Promise.

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

Anda dapat menggunakan promise 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
7 daviddavis Nov 28 2016 at 10:07

Dengan javaScript vanilla biasa, berikut adalah solusi untuk menjanjikan callback 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

Library Q oleh kriskowal menyertakan fungsi callback-to-promise. 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) {
});
4 user1852503 Aug 04 2016 at 07:45

Jika Anda memiliki beberapa fungsi yang menerima callback dan Anda ingin mereka mengembalikan promise, 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;
    }
}
4 PaulSpaulding Apr 12 2017 at 23:48

Di bawah node v7.6 + yang memiliki promise dan async bawaan:

// 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;
}
3 DoAsync Oct 13 2017 at 05:19

Di Node.js 8 Anda dapat menetapkan metode objek dengan cepat menggunakan modul npm ini:

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

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

Dengan objek:

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 native calldan applyuntuk mengikat beberapa konteks:

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

Anda dapat menggunakan Promise asli di ES6, sebagai contoh menangani 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, sehingga reject()tidak pernah dipanggil.

2 jituanlin Jul 30 2017 at 20:39

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 pertamanya.

Jadi, Anda bisa menulis fungsi untuk mengonversi fungsi dengan gaya 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 ringkasnya, contoh di atas digunakan ramda.js. Ramda.js adalah pustaka 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 menjadi 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 untuk Anda.

2 onmyway133 Oct 09 2018 at 20:35

Anda bisa 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

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

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

es6-promisify mengonversi fungsi berbasis panggilan balik menjadi fungsi berbasis janji.

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

const promisedFn = promisify(callbackedFn, args);

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

1 loretoparisi Nov 30 2017 at 06:34

Versi promisify saya dari suatu callbackfungsi adalah Pfungsinya:

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

1 Mzndako Jun 21 2019 at 17:38

Di bawah ini adalah implementasi bagaimana sebuah fungsi (callback API) dapat diubah menjadi sebuah 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');

1 JosiahNyarega Sep 25 2020 at 01:28

Dari masa depan 😄

Fungsi umum sederhana yang biasanya saya gunakan.

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

Bagaimana cara menggunakannya

   promisify(fn, arg1, arg2)

Anda mungkin tidak mencari jawaban ini, tetapi ini akan membantu memahami cara kerja bagian dalam dari utilitas yang tersedia