Làm cách nào để chuyển đổi API gọi lại hiện tại thành lời hứa?


721

Tôi muốn làm việc với các lời hứa nhưng tôi có API gọi lại theo định dạng như:

1. Tải DOM hoặc sự kiện một lần khác:

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

};

2. Gọi lại đơn giản:

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

3. Gọi lại kiểu nút ("gật đầu"):

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

4. Toàn bộ thư viện với các cuộc gọi lại kiểu nút:

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

Làm cách nào để tôi làm việc với API trong các lời hứa, làm cách nào để "quảng bá" nó?


Tôi đã đăng câu trả lời của riêng mình nhưng câu trả lời mở rộng về cách thực hiện điều này cho một thư viện cụ thể hoặc trong nhiều trường hợp và chỉnh sửa cũng rất được hoan nghênh.
Benjamin Gruenbaum

@Bergi Đó là một ý tưởng thú vị, tôi đã cố gắng đưa ra một câu trả lời chung sử dụng hai cách tiếp cận phổ biến (Promise constructor và deferred object). Tôi đã cố gắng đưa ra hai phương án trong câu trả lời. Tôi đồng ý rằng RTFMing giải quyết vấn đề này nhưng chúng tôi thường gặp phải vấn đề này cả ở đây và trong trình theo dõi lỗi nên tôi đã tìm ra một 'câu hỏi kinh điển' - tôi nghĩ RTFMing giải quyết khoảng 50% các vấn đề trong thẻ JS: D If bạn có một cái nhìn sâu sắc thú vị để đóng góp trong câu trả lời hoặc chỉnh sửa nó sẽ được đánh giá rất cao.
Benjamin Gruenbaum

Có tạo ra một new Promisebất kỳ chi phí đáng kể? Tôi muốn bọc tất cả các hàm Noje.js đồng bộ của mình trong một Promise để xóa tất cả mã đồng bộ khỏi ứng dụng Node của tôi, nhưng đây có phải là cách thực hành tốt nhất? Nói cách khác, một hàm chấp nhận một đối số tĩnh (ví dụ: một chuỗi) và trả về một kết quả được tính toán, tôi có nên gói nó trong một lời hứa không? ... Tôi đã đọc ở đâu đó rằng bạn không nên có bất kỳ mã đồng bộ nào trong Nodejs.
Ronnie Royston

1
@RonRoyston không, không nên kết thúc các cuộc gọi đồng bộ bằng các lời hứa - chỉ các cuộc gọi không đồng bộ có thể thực hiện I / O
Benjamin Gruenbaum

Câu trả lời:


744

Hứa có trạng thái, chúng bắt đầu như đang chờ xử lý và có thể giải quyết:

  • hoàn thành nghĩa là tính toán hoàn thành thành công.
  • từ chối có nghĩa là tính toán thất bại.

Các hàm trả về hứa sẽ không bao giờ ném , thay vào đó chúng nên trả về các từ chối. Ném từ hàm trả về lời hứa sẽ buộc bạn sử dụng cả a } catch { a .catch. Những người sử dụng API được quảng cáo không mong đợi những lời hứa sẽ ném. Nếu bạn không chắc chắn cách API async hoạt động trong JS - vui lòng xem câu trả lời này trước.

1. Tải DOM hoặc sự kiện một lần khác:

Vì vậy, tạo lời hứa thường có nghĩa là chỉ định khi họ giải quyết - điều đó có nghĩa là khi họ chuyển sang giai đoạn hoàn thành hoặc bị từ chối để cho biết dữ liệu có sẵn (và có thể được truy cập cùng .then).

Với các triển khai lời hứa hiện đại hỗ trợ nhà Promisexây dựng như lời hứa ES6 bản địa:

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

Sau đó, bạn sẽ sử dụng lời hứa kết quả như vậy:

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

Với các thư viện hỗ trợ hoãn lại (Hãy sử dụng $ q cho ví dụ này tại đây, nhưng chúng tôi cũng sẽ sử dụng jQuery sau):

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

Hoặc với một API như jQuery, nối vào một sự kiện xảy ra một lần:

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

2. Gọi lại đơn giản:

Các API này khá phổ biến vì các cuộc gọi lại cũng rất phổ biến trong JS. Hãy xem xét trường hợp phổ biến của việc có onSuccessonFail:

function getUserData(userId, onLoad, onFail) { 

Với các triển khai lời hứa hiện đại hỗ trợ nhà Promisexây dựng như lời hứa ES6 bản địa:

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

Với các thư viện hỗ trợ hoãn lại (Hãy sử dụng jQuery cho ví dụ này tại đây, nhưng chúng tôi cũng đã sử dụng $ q ở trên):

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

jQuery cũng cung cấp một $.Deferred(fn)biểu mẫu, có lợi thế là cho phép chúng ta viết một biểu thức mô phỏng rất chặt chẽ new Promise(fn)biểu mẫu, như sau:

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

Lưu ý: Ở đây chúng tôi khai thác thực tế là các phương thức resolverejectphương thức trì hoãn của jQuery là "có thể tháo rời"; I E. chúng bị ràng buộc với thể hiện của jQuery.Deferred (). Không phải tất cả các lib đều cung cấp tính năng này.

3. Gọi lại kiểu nút ("gật đầu"):

Các cuộc gọi lại kiểu nút (nút bấm) có một định dạng cụ thể trong đó các cuộc gọi lại luôn là đối số cuối cùng và tham số đầu tiên của nó là một lỗi. Trước tiên hãy quảng cáo một cách thủ công:

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

Đến:

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

Với việc trì hoãn, bạn có thể thực hiện các thao tác sau (hãy sử dụng Q cho ví dụ này, mặc dù hiện tại Q hỗ trợ cú pháp mới mà bạn nên chọn ):

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

Nói chung, bạn không nên quảng cáo mọi thứ theo cách thủ công quá nhiều, hầu hết các thư viện hứa hẹn được thiết kế với Node cũng như các lời hứa riêng trong Node 8+ đều có phương pháp tích hợp để quảng cáo các nút bấm. Ví dụ

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

4. Toàn bộ thư viện với các cuộc gọi lại kiểu nút:

Không có quy tắc vàng ở đây, bạn hứa hẹn từng cái một. Tuy nhiên, một số triển khai lời hứa cho phép bạn thực hiện hàng loạt việc này, ví dụ như trong Bluebird, chuyển đổi API gật đầu thành API hứa hẹn cũng đơn giản như:

Promise.promisifyAll(API);

Hoặc với những lời hứa riêng trong 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}), {});

Ghi chú:

  • Tất nhiên, khi bạn ở trong một người .thenxử lý, bạn không cần phải quảng bá mọi thứ. Trả lại lời hứa từ người .thenxử lý sẽ giải quyết hoặc từ chối với giá trị của lời hứa đó. Ném từ một người .thenxử lý cũng là một thực hành tốt và sẽ từ chối lời hứa - đây là lời hứa ném an toàn nổi tiếng.
  • Trong onloadtrường hợp thực tế , bạn nên sử dụng addEventListenerchứ không phải onX.

Benjamin, tôi đã chấp nhận lời mời của bạn để chỉnh sửa và thêm một ví dụ jQuery khác vào trường hợp 2. Nó sẽ cần xem xét ngang hàng trước khi nó xuất hiện. Hy vọng bạn thích nó.
Roamer-1888

@ Roamer-1888 nó đã bị từ chối vì tôi không thấy và chấp nhận nó kịp thời. Đối với những gì đáng giá tôi không nghĩ rằng bổ sung là quá phù hợp mặc dù hữu ích.
Benjamin Gruenbaum

2
Benjamin, dù có hay không resolve()reject()được viết để có thể tái sử dụng, tôi mạo hiểm rằng bản chỉnh sửa được đề xuất của tôi có liên quan vì nó cung cấp một ví dụ jQuery về biểu mẫu $.Deferred(fn), nếu không thì thiếu. Nếu chỉ có một ví dụ jQuery được bao gồm, thì tôi khuyên bạn nên sử dụng dạng này chứ không phải var d = $.Deferred();v.v. vì mọi người nên được khuyến khích sử dụng $.Deferred(fn)biểu mẫu bị bỏ qua , cộng với, trong một câu trả lời như thế này, nó đặt jQuery ngang tầm với libs sử dụng Mô hình xây dựng tiết lộ .
Roamer-1888

Heh, để được 100% công bằng tôi không biết jQuery cho phép bạn làm $.Deferred(fn), nếu bạn chỉnh sửa trong thay vì ví dụ hiện trong 15 phút tiếp theo tôi chắc chắn rằng tôi có thể cố gắng để chấp nhận nó đúng thời hạn :)
Benjamin Gruenbaum

7
Đây là một câu trả lời tuyệt vời. Bạn có thể muốn cập nhật nó bằng cách đề cập thêm util.promisify, Node.js sẽ thêm vào lõi của nó bắt đầu từ RC 8.0.0. Nó hoạt động không khác nhiều so với Bluebird Promise.promisify, nhưng có ưu điểm là không yêu cầu phụ thuộc bổ sung, trong trường hợp bạn chỉ muốn Promise bản địa. Tôi đã viết một bài đăng trên blog về produc.promisify cho bất cứ ai muốn đọc thêm về chủ đề này.
Bruno

55

Hôm nay, tôi có thể sử dụng Promisetrong Node.jsnhư một phương pháp Javascript đơn giản.

Một ví dụ đơn giản và cơ bản để Promise(với cách KISS ):

Mã API Async đồng bằng Javascript:

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

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

    successCallback( number / divider )

}

Promise Mã API Async:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(Tôi khuyên bạn nên ghé thăm nguồn tuyệt đẹp này )

Cũng Promisecó thể được sử dụng với nhau async\awaittrong ES7để làm cho sự chờ đợi dòng chương trình cho một fullfiledkết quả như sau:

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

Một cách sử dụng khác với cùng mã bằng cách sử dụng .then()phương thức

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

Promisecũng có thể được sử dụng trên bất kỳ nền tảng nào dựa trên Node.js như thế nào react-native.

Phần thưởng : Phương thức kết hợp
( Phương thức gọi lại được giả sử có hai tham số là lỗi và kết quả)

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 )

     })

}

Phương pháp trên có thể đáp ứng kết quả cho cuộc gọi lại thời trang cũ và cách sử dụng Promise.

Hi vọng điêu nay co ich.


3
Chúng dường như không chỉ ra cách chuyển đổi thành lời hứa.
Dmitri Zaitsev

33

Trước khi chuyển đổi một chức năng như lời hứa trong 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)
})

Sau khi chuyển đổi nó

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

Trong trường hợp bạn cần xử lý nhiều yêu cầu

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

Tôi không nghĩ rằng window.onloadđề xuất của @Benjamin sẽ hoạt động mọi lúc, vì nó không phát hiện xem nó có được gọi sau khi tải không. Tôi đã bị cắn bởi điều đó nhiều lần. Đây là một phiên bản luôn luôn hoạt động:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);

1
không nên sử dụng nhánh "đã hoàn thành" setTimeout(resolve, 0)(hoặc setImmediate, nếu có) để đảm bảo rằng nó được gọi là không đồng bộ?
Alnitak

5
@Alnitak Gọi resolveđồng bộ là ổn. Các thentrình xử lý của Promise được đảm bảo bởi khung được gọi là không đồng bộ , bất kể resolveđược gọi là đồng bộ.
Jeff Bowman

15

Node.js 8.0.0 bao gồm một util.promisify()API mới cho phép các API kiểu gọi lại tiêu chuẩn của Node.js được gói trong một hàm trả về một Promise. Một ví dụ sử dụng util.promisify()được hiển thị dưới đây.

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

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

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

Xem phần Hỗ trợ được cải thiện cho Lời hứa


2
Đã có hai câu trả lời mô tả điều này, tại sao lại đăng câu thứ ba?
Benjamin Gruenbaum

1
Chỉ vì phiên bản nút đó hiện đã được phát hành và tôi đã báo cáo mô tả và liên kết tính năng "chính thức".
Gian Marco Gherardi

14

Trong ứng cử viên phát hành cho Node.js 8.0.0, có một tiện ích mới, util.promisify(tôi đã viết về produc.promisify ), gói gọn khả năng của việc quảng bá bất kỳ chức năng nào.

Nó không khác nhiều so với các cách tiếp cận được đề xuất trong các câu trả lời khác, nhưng có ưu điểm là phương pháp cốt lõi và không yêu cầu phụ thuộc bổ sung.

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

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

Sau đó, bạn có một readFilephương thức trả về một bản địa Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);

1
Hey, tôi (OP) thực sự đã đề xuất util.promisifyhai lần (trở lại vào năm 2014 khi câu hỏi này được viết và vài tháng trước - mà tôi đã cố gắng trở thành thành viên cốt lõi của Node và là phiên bản hiện tại chúng tôi có trong Node). Vì nó chưa có sẵn công khai - tôi chưa thêm nó vào câu trả lời này. Chúng tôi sẽ đánh giá rất cao phản hồi sử dụng mặc dù và tìm hiểu một số cạm bẫy để có tài liệu tốt hơn cho việc phát hành :)
Benjamin Gruenbaum

1
Ngoài ra, bạn có thể muốn thảo luận về cờ tùy chỉnh để quảng cáo util.promisifytrong bài đăng trên blog của mình :)
Benjamin Gruenbaum

@BenjaminGruenbaum Bạn có nghĩa là thực tế là sử dụng util.promisify.custombiểu tượng có thể ghi đè lên kết quả của produc.promisify? Thành thật mà nói đây là một lỗi cố ý, vì tôi chưa thể tìm thấy một trường hợp sử dụng hữu ích. Có lẽ bạn có thể cho tôi một số đầu vào?
Bruno

1
Chắc chắn, hãy xem xét các API như fs.existshoặc API không tuân theo quy ước Node - một bluebird Promise.promisify sẽ hiểu sai, nhưng util.promisifylàm cho chúng đúng.
Benjamin Gruenbaum

7

Bạn có thể sử dụng lời hứa gốc JavaScript với Node JS.

Liên kết mã Cloud 9 của tôi: https://ide.c9.io/adx2804/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

Với javaScript vanilla cũ, đây là một giải pháp để quảng bá cho một cuộc gọi lại 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

Thư viện Q của kriskowal bao gồm các hàm gọi lại để hứa. Một phương pháp như thế này:

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

có thể được chuyển đổi với Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});

1
Các câu trả lời kinh điển đã đề cập Q.denodeify. Chúng ta có cần nhấn mạnh người trợ giúp thư viện?
Bergi

3
tôi thấy điều này hữu ích khi google về việc quảng cáo trong các khách hàng tiềm năng ở đây
Ed Sykes

4

Khi bạn có một vài chức năng thực hiện cuộc gọi lại và bạn muốn họ trả lại lời hứa thay vào đó bạn có thể sử dụng chức năng này để thực hiện chuyển đổi.

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

Trong nút v7.6 + đã được xây dựng trong lời hứa và không đồng bộ:

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

Cách sử dụng:

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

Trong Node.js 8, bạn có thể quảng cáo các phương thức đối tượng một cách nhanh chóng bằng mô-đun npm này:

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

Nó sử dụng produc.promisifyProxy để các đối tượng của bạn không thay đổi. Ghi nhớ cũng được thực hiện với việc sử dụng WeakMaps). Dưới đây là một số ví dụ:

Với các đối tượng:

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

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

Với các chức năng:

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

Bạn thậm chí có thể sử dụng nguồn gốc callapplyđể ràng buộc một số bối cảnh:

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

2

Bạn có thể sử dụng Promise gốc trong ES6, để xử lý ví dụ với 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
    });

}

Trong ví dụ này, Lời hứa không có lý do để thất bại, vì vậy reject()không bao giờ được gọi.


2

Hàm kiểu gọi lại luôn như thế này (hầu như tất cả các hàm trong node.js đều là kiểu này):

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

Phong cách này có tính năng tương tự:

  1. chức năng gọi lại được thông qua bởi đối số cuối cùng.

  2. hàm gọi lại luôn chấp nhận đối tượng lỗi là đối số đầu tiên.

Vì vậy, bạn có thể viết một hàm để chuyển đổi một hàm với kiểu này như sau:

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

Để ngắn gọn hơn, ví dụ trên đã sử dụng ramda.js. Ramda.js là một thư viện tuyệt vời cho lập trình chức năng. Trong đoạn mã trên, chúng tôi đã sử dụng nó áp dụng (như javascript function.prototype.apply) và chắp thêm (như javascript function.prototype.push). Vì vậy, chúng ta có thể chuyển đổi hàm kiểu gọi lại để hứa với hàm kiểu bây giờ:

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

Hàm toPromisecheckErr được sở hữu bởi thư viện berserk , đó là một ngã ba thư viện lập trình chức năng của ramda.js (do tôi tạo).

Hy vọng câu trả lời này hữu ích cho bạn.


2

Bạn có thể làm một cái gì đó như thế này

// @flow

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

export default toPromise

Sau đó sử dụng nó

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

  console.log(friends)
}

2
Này, tôi không chắc điều này bổ sung vào câu trả lời hiện tại (có thể làm rõ?). Ngoài ra, không cần thử / bắt bên trong hàm tạo lời hứa (nó tự động thực hiện điều này cho bạn). Cũng không rõ chức năng này hoạt động như thế nào (gọi cuộc gọi lại với một đối số duy nhất về thành công? Lỗi được xử lý như thế nào?)
Benjamin Gruenbaum


1

Phiên bản hứa hẹn của tôi là một callbackchức Pnăng:

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

Các Pchức năng yêu cầu chữ ký callback phải là callback(error,result).


1
Điều này có lợi thế gì so với bản địa hứa hẹn hoặc hơn các câu trả lời ở trên?
Benjamin Gruenbaum

Bạn có ý nghĩa gì đối với việc quảng bá bản địa?
loretoparisi


tất nhiên rồi Chỉ và ví dụ để hiển thị các ý tưởng cơ bản. Trong thực tế, bạn có thể thấy ngay cả bản gốc yêu cầu chữ ký hàm phải được xác định như thế nào (err, value) => ...hoặc bạn phải xác định một tùy chỉnh (xem Hàm được quảng cáo tùy chỉnh). Cảm ơn bạn tốt Catcha.
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))); };sẽ làm điều tương tự như của bạn và nó đơn giản hơn nhiều.
Patrick Roberts

1

Dưới đây là cách triển khai cách một hàm (API gọi lại) có thể được chuyển đổi thành một lời hứa.

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

-2

Nó giống như trễ 5 năm, nhưng tôi muốn đăng ở đây phiên bản hứa hẹn của mình có chức năng từ API gọi lại và biến chúng thành lời hứa

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

Hãy xem phiên bản rất đơn giản này tại đây: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
Đó không phải là một lời hứa, nó không xâu chuỗi, xử lý các lỗi trong cuộc gọi lại hoặc chấp nhận tham số thứ hai sau đó ...
Benjamin Gruenbaum
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.