ฉันจะแปลง API การเรียกกลับที่มีอยู่เป็นสัญญาได้อย่างไร


721

ฉันต้องการทำงานกับคำสัญญา แต่ฉันมี callback API ในรูปแบบดังนี้:

1. โหลด DOM หรือเหตุการณ์ครั้งเดียวอื่น ๆ :

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

};

2. โทรกลับธรรมดา:

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

3. การเรียกกลับสไตล์โหนด ("nodeback"):

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

4. ไลบรารีทั้งหมดที่มีการเรียกกลับสไตล์โหนด:

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

ฉันจะทำงานกับ API ในสัญญาได้อย่างไรฉันจะ "แนะนำ" มันได้อย่างไร


ฉันโพสต์คำตอบของฉันเอง แต่คำตอบที่เพิ่มขึ้นเกี่ยวกับวิธีการทำเช่นนี้สำหรับห้องสมุดเฉพาะหรือในสถานการณ์ที่มากขึ้นและการแก้ไขก็ยินดีต้อนรับเช่นกัน
Benjamin Gruenbaum

@Bergi นั่นเป็นความคิดที่น่าสนใจฉันพยายามที่จะให้คำตอบทั่วไปที่ใช้สองวิธีร่วมกัน (ตัวสร้างสัญญาและวัตถุที่เลื่อนออกไป) ฉันพยายามให้คำตอบทั้งสองทางเลือก ฉันยอมรับว่า RTFMing แก้ปัญหานี้ แต่เราพบปัญหานี้บ่อยครั้งทั้งที่นี่และในตัวติดตามบั๊กดังนั้นฉันจึงคิดว่ามี 'คำถามมาตรฐาน' อยู่ - ฉันคิดว่า RTFMing แก้ปัญหาได้ประมาณ 50% ของแท็ก JS: D ถ้า คุณมีข้อมูลเชิงลึกที่น่าสนใจที่จะมีส่วนร่วมในการตอบหรือแก้ไขมันจะได้รับการชื่นชมมาก
Benjamin Gruenbaum

การสร้างการnew Promiseเพิ่มค่าใช้จ่ายที่สำคัญหรือไม่ ฉันต้องการห่อฟังก์ชั่น Noje.js แบบซิงโครนัสทั้งหมดของฉันใน Promise เพื่อลบโค้ดซิงโครนัสทั้งหมดออกจากแอป Node ของฉัน แต่นี่เป็นวิธีปฏิบัติที่ดีที่สุดหรือไม่ กล่าวอีกนัยหนึ่งฟังก์ชั่นที่รับอาร์กิวเมนต์คงที่ (เช่นสตริง) และส่งคืนผลลัพธ์ที่คำนวณได้ฉันควรสรุปด้วยคำสัญญาหรือไม่? ... ฉันอ่านบางที่ที่คุณไม่ควรมีรหัสซิงโครนัสใน Nodejs
Ronnie Royston

1
@RonRoyston ไม่มันไม่ใช่ความคิดที่ดีที่จะใช้การโทรแบบซิงโครนัสพร้อมสัญญา - การโทรแบบอะซิงโครนัสเท่านั้นที่สามารถใช้ I / O ได้
Benjamin Gruenbaum

คำตอบ:


744

สัญญามีสถานะพวกเขาเริ่มเป็นรอดำเนินการและสามารถชำระให้กับ:

  • เติมเต็มความหมายว่าการคำนวณเสร็จสมบูรณ์แล้ว
  • ปฏิเสธหมายความว่าการคำนวณล้มเหลว

ฟังก์ชั่นส่งคืนที่สัญญาไว้ไม่ควรทิ้งแต่ควรส่งคืนการปฏิเสธแทน โยนจากสัญญาฟังก์ชั่นที่กลับมาจะบังคับให้คุณใช้ทั้งสอง} catch { และ .catchผู้ที่ใช้ API ที่ได้รับการรับรองไม่คาดว่าจะเกิดสัญญา หากคุณไม่แน่ใจว่า API ของ async ทำงานอย่างไรใน JS - โปรดดูคำตอบนี้ก่อน

1. โหลด DOM หรือเหตุการณ์ครั้งเดียวอื่น ๆ :

ดังนั้นการสร้างสัญญาโดยทั่วไปหมายถึงการระบุเมื่อพวกเขาชำระ - นั่นหมายถึงเมื่อพวกเขาย้ายไปยังเฟสที่ถูกเติมเต็มหรือถูกปฏิเสธเพื่อระบุว่ามีข้อมูล (และสามารถเข้าถึงได้ด้วย.then)

ด้วยการใช้งานสัญญาที่ทันสมัยที่สนับสนุนPromiseนวกรรมิกเช่น ES6 ดั้งเดิม:

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

จากนั้นคุณจะใช้สัญญาที่เป็นผลลัพธ์ดังนี้:

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

ด้วยไลบรารี่ที่รองรับการเลื่อนเวลา (ลองใช้ $ q สำหรับตัวอย่างนี้ที่นี่ แต่เราจะใช้ jQuery ในภายหลัง):

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

หรือด้วย jQuery เช่น API การเชื่อมโยงเหตุการณ์ที่เกิดขึ้นหนึ่งครั้ง:

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

2. โทรกลับธรรมดา:

API เหล่านี้ค่อนข้างใช้กันทั่วไปเนื่องจาก ... การเรียกกลับเป็นเรื่องปกติใน JS ลองดูกรณีทั่วไปของการมีonSuccessและonFail:

function getUserData(userId, onLoad, onFail) { 

ด้วยการใช้งานสัญญาที่ทันสมัยที่สนับสนุนPromiseนวกรรมิกเช่น ES6 ดั้งเดิม:

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

ด้วยไลบรารี่ที่รองรับการเลื่อนเวลา (ลองใช้ jQuery สำหรับตัวอย่างนี้ที่นี่ แต่เราใช้ $ q ด้านบนด้วย):

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

jQuery ยังเสนอ$.Deferred(fn)รูปแบบซึ่งมีข้อดีของการอนุญาตให้เราเขียนนิพจน์ที่เลียนnew Promise(fn)แบบฟอร์มอย่างใกล้ชิดดังนี้:

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

หมายเหตุ: ที่นี่เราใช้ประโยชน์จากข้อเท็จจริงที่ว่า jQuery deferred resolveและrejectเมธอดเป็น "detachable"; กล่าวคือ พวกเขาถูกผูกไว้กับอินสแตนซ์ของ jQuery.Deferred () ไม่ใช่ libs ทั้งหมดที่เสนอคุณสมบัตินี้

3. การเรียกกลับสไตล์โหนด ("nodeback"):

ลักษณะการเรียกกลับของโหนด (nodebacks) มีรูปแบบเฉพาะที่การเรียกกลับเป็นอาร์กิวเมนต์สุดท้ายเสมอและพารามิเตอร์แรกเป็นข้อผิดพลาด ให้เรามาแนะนำคนด้วยตนเองก่อน:

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

ถึง:

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

ด้วยการรอการตัดบัญชีคุณสามารถทำสิ่งต่อไปนี้ (ลองใช้ Q สำหรับตัวอย่างนี้ถึงแม้ว่าตอนนี้ Q จะรองรับไวยากรณ์ใหม่ที่คุณควรจะชอบ ):

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

โดยทั่วไปคุณไม่ควรแนะนำสิ่งต่าง ๆ ด้วยตนเองมากเกินไปห้องสมุดสัญญาที่ได้รับการออกแบบโดยคำนึงถึง Node เช่นเดียวกับสัญญาดั้งเดิมใน Node 8+ มีวิธีการสร้างโหนดที่มีอยู่ในตัว ตัวอย่างเช่น

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

4. ไลบรารีทั้งหมดที่มีการเรียกกลับสไตล์โหนด:

ไม่มีกฎทองคำที่นี่คุณแนะนำพวกเขาทีละคน อย่างไรก็ตามการใช้งานสัญญาบางอย่างอนุญาตให้คุณทำสิ่งนี้เป็นจำนวนมากตัวอย่างเช่นใน Bluebird การแปลง nodeback API เป็น API สัญญาเป็นเรื่องง่ายเพียง:

Promise.promisifyAll(API);

หรือด้วยสัญญาพื้นเมืองในโหนด :

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

หมายเหตุ:

  • แน่นอนเมื่อคุณอยู่ในผู้.thenดูแลคุณไม่จำเป็นต้องแนะนำสิ่งต่าง ๆ การส่งคืนสัญญาจาก.thenผู้จัดการจะแก้ไขหรือปฏิเสธด้วยมูลค่าของสัญญานั้น การขว้างจากผู้.thenดูแลก็เป็นแนวปฏิบัติที่ดีและจะปฏิเสธสัญญา - นี่คือสัญญาการโยนที่ปลอดภัย
  • ในความเป็นจริงonloadในกรณีนี้คุณควรใช้มากกว่าaddEventListeneronX

เบนจามินฉันยอมรับคำเชิญของคุณเพื่อแก้ไขและเพิ่มตัวอย่าง jQuery เพิ่มเติมให้กับกรณีที่ 2 มันจะต้องมีการตรวจสอบโดยเพื่อนก่อนที่จะปรากฏขึ้น หวังว่าคุณจะชอบมัน.
Roamer-1888

@ Roamer-1888 มันถูกปฏิเสธเนื่องจากฉันไม่เห็นและยอมรับมันในเวลา สำหรับสิ่งที่คุ้มค่าฉันไม่คิดว่าการเพิ่มนั้นเกี่ยวข้องเกินไปแม้ว่าจะมีประโยชน์
Benjamin Gruenbaum

2
เบ็นจามินไม่ว่าจะเขียนresolve()และreject()นำไปใช้ซ้ำได้หรือไม่ก็ตามฉันคิดว่าการแก้ไขที่ฉันแนะนำนั้นมีความเกี่ยวข้องเพราะเป็นตัวอย่างของแบบฟอร์ม jQuery $.Deferred(fn)ซึ่งขาดไป หากมีตัวอย่าง jQuery เพียงตัวอย่างเดียวฉันขอแนะนำให้ใช้แบบฟอร์มนี้แทนแบบvar d = $.Deferred();อื่น ๆ เนื่องจากผู้ใช้ควรสนับสนุนให้ใช้$.Deferred(fn)แบบฟอร์มที่ถูกละเลยบ่อยครั้งรวมทั้งคำตอบเช่นนี้มันทำให้ jQuery มากกว่า libs ที่ใช้รูปแบบเปิดเผยตัวสร้าง
Roamer-1888

เฮ้งานนี้ยุติธรรม 100% ฉันไม่รู้ jQuery ให้คุณทำ$.Deferred(fn)ถ้าคุณแก้ไขมันแทนที่จะเป็นตัวอย่างที่มีอยู่ใน 15 นาทีข้างหน้าฉันแน่ใจว่าฉันสามารถลองอนุมัติได้ทันเวลา :)
Benjamin Gruenbaum

7
นี่คือคำตอบที่ดี คุณอาจต้องการอัปเดตโดยกล่าวถึงutil.promisifyด้วยว่า Node.js จะเพิ่มไปยังแกนของมันเริ่มต้นจาก RC 8.0.0 การทำงานของมันไม่ได้แตกต่างจาก Bluebird มากนักPromise.promisifyแต่มีข้อได้เปรียบที่ไม่จำเป็นต้องมีการพึ่งพาเพิ่มเติมในกรณีที่คุณต้องการแค่ Promise ดั้งเดิม ฉันเขียนโพสต์บล็อกเกี่ยวกับutil.promisifyสำหรับทุกคนที่ต้องการอ่านเพิ่มเติมในหัวข้อ
บรูโน่

55

วันนี้ผมสามารถใช้PromiseในการNode.jsเป็นวิธีการ Javascript ธรรมดา

ตัวอย่างที่ง่ายและพื้นฐานถึงPromise(ด้วยวิธีKISS ):

รหัส API Javascript Async แบบธรรมดา :

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

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

    successCallback( number / divider )

}

Promise รหัส 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 )

     })

}

(ฉันแนะนำให้เยี่ยมชมแหล่งที่สวยงามนี้ )

นอกจากนี้ยังPromiseสามารถนำมาใช้กับการร่วมกันasync\awaitในการES7ที่จะทำให้การรอคอยการไหลของโปรแกรมสำหรับfullfiledผลในลักษณะต่อไปนี้:

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

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

    })

}


async function foo () {

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

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

}


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

การใช้งานอื่นที่มีรหัสเดียวกันโดยใช้.then()วิธีการ

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

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

    })

}


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

Promiseนอกจากนี้ยังสามารถนำมาใช้บนแพลตฟอร์มใดที่เป็นไปตาม Node.js react-nativeเช่น

โบนัส : วิธีไฮบริด
( วิธีการโทรกลับจะถือว่ามีสองพารามิเตอร์เป็นข้อผิดพลาดและผลลัพธ์)

function divisionAPI (number, divider, callback) {

    return new Promise(function (fulfilled, rejected) {

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

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

     })

}

วิธีการข้างต้นสามารถตอบสนองผลการโทรกลับแบบเก่าและการใช้งานสัญญา

หวังว่านี่จะช่วยได้


3
สิ่งเหล่านี้ดูเหมือนจะไม่แสดงให้เห็นว่าจะเปลี่ยนเป็นสัญญาได้อย่างไร
Dmitri Zaitsev

33

ก่อนที่จะแปลงฟังก์ชันตามสัญญาใน 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)
})

หลังจากแปลงมัน

var request = require('request');

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


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

ในกรณีที่คุณต้องจัดการกับคำขอหลายรายการ

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

Promise.all(allRequests).then(function (results) {
  console.log(results);//result will be array which contains each promise response
}).catch(function (err) {
  console.log(err)
});

23

ฉันไม่คิดว่าwindow.onloadคำแนะนำของ @Benjamin จะทำงานได้ตลอดเวลาเนื่องจากไม่พบว่ามีการเรียกใช้หลังจากการโหลดหรือไม่ หลายครั้งที่ฉันถูกกัด นี่คือรุ่นที่ควรใช้งานได้เสมอ:

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

1
ไม่ควรใช้สาขา "เสร็จสมบูรณ์แล้ว" setTimeout(resolve, 0)(หรือsetImmediateถ้ามี) เพื่อให้แน่ใจว่ามันเรียกว่าแบบอะซิงโครนัส?
Alnitak

5
@Alnitak การโทรresolveพร้อมกันนั้นใช้ได้ ตัวthenจัดการของ Promise รับประกันโดยกรอบงานที่จะเรียกว่าแบบอะซิงโครนัสไม่ว่าจะเรียกว่าแบบซิงโครนัสหรือไม่resolveก็ตาม
Jeff Bowman

15

Node.js 8.0.0 มีutil.promisify()API ใหม่ที่อนุญาตให้ห่อหุ้มสไตล์การโทรกลับของมาตรฐาน Node.js ในฟังก์ชันที่ส่งคืน Promise ตัวอย่างการใช้งานutil.promisify()แสดงไว้ด้านล่าง

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

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

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

ดูการสนับสนุนที่ได้รับการปรับปรุงสำหรับสัญญา


2
มีสองคำตอบที่อธิบายสิ่งนี้อยู่แล้วเหตุใดจึงโพสต์คำตอบที่สาม
Benjamin Gruenbaum

1
เพียงเพราะโหนดเวอร์ชันนั้นเปิดตัวแล้วและฉันได้รายงานคำอธิบายและลิงก์ "เป็นทางการ" แล้ว
Gian Marco Gherardi

14

ในรีลีสผู้สมัครสำหรับ Node.js 8.0.0 มียูทิลิตี้ใหม่util.promisify(ฉันได้เขียนเกี่ยวกับutil.promisify ) ซึ่งจะสรุปความสามารถในการทำหน้าที่อะไรก็ตาม

มันไม่แตกต่างจากวิธีที่เสนอในคำตอบอื่น ๆ แต่มีข้อดีของการเป็นวิธีหลักและไม่ต้องการการพึ่งพาเพิ่มเติม

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

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

แล้วคุณได้วิธีการที่ส่งกลับพื้นเมืองreadFilePromise

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

1
เฮ้ฉัน (OP) แนะนำจริง ๆutil.promisifyสองครั้ง (ย้อนกลับไปในปี 2014 เมื่อคำถามนี้ถูกเขียนและไม่กี่เดือนที่ผ่านมา - ซึ่งฉันผลักให้เป็นสมาชิกหลักของโหนดและเป็นรุ่นปัจจุบันที่เรามีในโหนด) เนื่องจากยังไม่เปิดเผยต่อสาธารณะ - ฉันยังไม่ได้เพิ่มคำตอบนี้ เราลึกจะขอบคุณข้อเสนอแนะการใช้งานและแม้ว่าจะได้รู้ว่าสิ่งที่ผิดพลาดบางอย่างอยู่ในลำดับที่จะมีเอกสารที่ดีกว่าสำหรับการปล่อย :)
เบนจามิน Gruenbaum

1
นอกจากนี้คุณอาจต้องการที่จะหารือเกี่ยวกับธงที่กำหนดเองสำหรับ promisifying กับutil.promisifyโพสต์ในบล็อกของคุณ :)
เบนจามิน Gruenbaum

@BenjaminGruenbaum คุณหมายถึงความจริงที่ว่าการใช้util.promisify.customสัญลักษณ์เป็นไปได้ที่จะแทนที่ผลลัพธ์ของ util.promisify หรือไม่? ความจริงแล้วนี่คือการพลาดโดยเจตนาเพราะฉันยังไม่สามารถค้นหากรณีการใช้งานที่มีประโยชน์ บางทีคุณสามารถให้อินพุตกับฉันได้บ้าง?
Bruno

1
แน่นอนว่าให้พิจารณา APIs fs.existsหรือ API ที่ไม่เป็นไปตามหลักการของ Node - นกบลูเบิร์ดPromise.promisify อาจทำให้พวกมันผิด แต่util.promisifyทำให้ถูกต้อง
Benjamin Gruenbaum

7

คุณสามารถใช้สัญญาดั้งเดิมของ JavaScript กับ Node JS

ลิงก์โค้ด My Cloud 9: https://ide.c9.io/adx2803/native-promises-in-node

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

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


var app = express();

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

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

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

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

//run webservice on browser : http://localhost:8081/listAlbums

7

ด้วย vanilla javaScript แบบธรรมดานี่คือวิธีแก้ปัญหาในการแนะนำการเรียกกลับ 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

ไลบรารี่ Q โดย kriskowal มีฟังก์ชั่นการโทรกลับเพื่อรับสัญญา วิธีการเช่นนี้:

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

สามารถแปลงด้วย Q.ninvoke

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

1
Q.denodeifyคำตอบที่ยอมรับแล้วกล่าว เราจำเป็นต้องเน้นผู้ช่วยห้องสมุดหรือไม่?
Bergi

3
ฉันพบว่ามีประโยชน์ในฐานะ google เกี่ยวกับ promisifying ในกลุ่มเป้าหมาย Q ที่นี่
Ed Sykes

4

เมื่อคุณมีฟังก์ชั่นบางอย่างที่ใช้โทรกลับและคุณต้องการให้พวกเขาคืนสัญญาแทนคุณสามารถใช้ฟังก์ชั่นนี้เพื่อทำการแปลง

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

ภายใต้โหนด v7.6 + ซึ่งมีสัญญาและ 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;

วิธีใช้:

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

ใน Node.js 8 คุณสามารถแนะนำวิธีการวัตถุได้ทันทีโดยใช้โมดูล npm นี้:

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

มันใช้util.promisifyและผู้รับมอบฉันทะเพื่อให้วัตถุของคุณไม่เปลี่ยนแปลง memoizationจะทำยังมีการใช้ WeakMaps) ที่ นี่คือตัวอย่างบางส่วน:

ด้วยวัตถุ:

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

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

ด้วยฟังก์ชั่น:

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

คุณสามารถใช้ native callและapplyผูกบริบท:

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

2

คุณสามารถใช้Native Promiseใน ES6 เพื่อจัดการกับ 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
    });

}

ในตัวอย่างนี้สัญญาไม่มีเหตุผลที่จะล้มเหลวดังนั้นจึงreject()ไม่เคยถูกเรียก


2

สไตล์โทรกลับฟังก์ชั่นอย่างนี้เสมอ (เกือบทุกฟังก์ชั่นใน Node.js เป็นรูปแบบนี้):

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

สไตล์นี้มีคุณสมบัติเหมือนกัน:

  1. ฟังก์ชันการเรียกกลับถูกส่งผ่านโดยอาร์กิวเมนต์สุดท้าย

  2. ฟังก์ชันการเรียกกลับยอมรับวัตถุข้อผิดพลาดเสมอเนื่องจากเป็นอาร์กิวเมนต์แรก

ดังนั้นคุณสามารถเขียนฟังก์ชั่นสำหรับแปลงฟังก์ชั่นที่มีลักษณะดังนี้:

const R =require('ramda')

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

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

เพื่อความกระชับตัวอย่างข้างต้นใช้ ramda.js Ramda.js เป็นห้องสมุดที่ยอดเยี่ยมสำหรับการเขียนโปรแกรมการทำงาน ในโค้ดข้างต้นเราใช้ก็ใช้ (เช่น JavaScript function.prototype.apply) และผนวก (เช่น JavaScript function.prototype.push) ดังนั้นเราสามารถแปลงฟังก์ชั่นสไตล์การโทรกลับเป็นสัญญาสไตล์ฟังก์ชั่นในขณะนี้:

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

toPromiseและcheckErrฟังก์ชั่นจะเป็นเจ้าของโดยบ้าดีเดือดห้องสมุดก็เป็นโปรแกรมการทำงานห้องสมุดส้อมโดยramda.js (สร้างโดยฉัน)

หวังว่าคำตอบนี้มีประโยชน์สำหรับคุณ


2

คุณสามารถทำอะไรเช่นนี้

// @flow

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

export default toPromise

จากนั้นใช้มัน

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

  console.log(friends)
}

2
เฮ้ฉันไม่แน่ใจว่าสิ่งนี้จะเพิ่มคำตอบที่มีอยู่ (อาจชี้แจง?) นอกจากนี้ไม่จำเป็นต้องมีการลอง / จับภายในตัวสร้างสัญญา (จะทำสิ่งนี้ให้คุณโดยอัตโนมัติ) มันยังไม่ชัดเจนว่าฟังก์ชั่นนี้ใช้งานอย่างไร (ที่เรียกการโทรกลับด้วยการโต้แย้งเพียงครั้งเดียวในความสำเร็จ? ข้อผิดพลาดจะจัดการอย่างไร?)
Benjamin Gruenbaum


1

รุ่นที่แน่นอนของฉันของcallbackฟังก์ชั่นคือPฟังก์ชั่น:

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

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

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

ฟังก์ชั่นต้องว่าลายเซ็นโทรกลับต้องPcallback(error,result)


1
สิ่งนี้มีประโยชน์อย่างไรต่อการแนะนำให้ใช้ภาษาพื้นเมืองหรือคำตอบข้างต้น
Benjamin Gruenbaum

คุณหมายความว่าอย่างไรสำหรับคนพื้นเมืองที่มีความโดดเด่น?
loretoparisi


ใช่แน่นอน :) เพียงและตัวอย่างเพื่อแสดงความคิดพื้นฐาน ในความเป็นจริงคุณสามารถดูว่าแม้แต่คนพื้นเมืองต้องการให้ลายเซ็นฟังก์ชั่นบางอย่างจะต้องกำหนดเช่น(err, value) => ...หรือคุณต้องกำหนดหนึ่งที่กำหนดเอง (ดูฟังก์ชั่นที่กำหนดเองสัญญา) ขอบคุณ 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))); };จะทำเช่นเดียวกับคุณและมันง่ายกว่ามาก
Patrick Roberts

1

ด้านล่างคือการใช้งานฟังก์ชัน (callback API) ที่สามารถแปลงเป็นสัญญาได้

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

// USE SCENARIO

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

var promisedFunction = promisify(apiFunction);

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

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

-2

มันเหมือนสาย 5 ปีแล้ว แต่ฉันต้องการโพสต์ที่นี่ในเวอร์ชัน promesify ซึ่งใช้ฟังก์ชั่นจาก callbacks API และเปลี่ยนเป็นสัญญา

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

ดูเวอร์ชั่นง่าย ๆ นี้ได้ที่นี่: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
นั่นไม่ใช่คำสัญญามันไม่
Benjamin Gruenbaum
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.