Bagaimana cara mengonversi API panggilan balik yang ada menjadi promise?
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
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 Promise
konstruktor 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 onSuccess
dan onFail
:
function getUserData(userId, onLoad, onFail) { …
Dengan implementasi promise modern yang mendukung Promise
konstruktor 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 resolve
and reject
method 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
.then
handler anda tidak perlu menjanjikan banyak hal. Mengembalikan janji dari.then
pawang akan menyelesaikan atau menolak dengan nilai janji itu. Melempar dari.then
pawang juga merupakan praktik yang baik dan akan menolak janji - ini adalah keselamatan melempar janji yang terkenal. - Dalam
onload
kasus aktual , Anda harus menggunakanaddEventListener
daripadaonX
.
Hari ini, saya dapat menggunakan Promise
di Node.js
sebagai 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 Promise
dapat digunakan dengan together async\await
in ES7
untuk membuat alur program menunggu fullfiled
hasil 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) })
Promise
juga 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.
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)
});
Saya rasa window.onload
saran @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);
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) => { /** ... **/ });
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 readFile
metode yang mengembalikan native Promise
.
readFile('./notes.txt')
.then(txt => console.log(txt))
.catch(...);
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
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);
});
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) {
});
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;
}
}
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;
}
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 call
dan apply
untuk mengikat beberapa konteks:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
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.
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:
fungsi callback dilewatkan oleh argumen terakhir.
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.
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)
}
es6-promisify
mengonversi fungsi berbasis panggilan balik menjadi fungsi berbasis janji.
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
Versi promisify saya dari suatu callback
fungsi adalah P
fungsinya:
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 P
Fungsi mensyaratkan bahwa tanda tangan callback harus callback(error,result)
.
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');
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