簡體   English   中英

如何將現有回調 API 轉換為承諾?

[英]How do I convert an existing callback API to promises?

我想使用 promises,但我有一個回調 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,如何“承諾”它?

Promise 具有狀態,它們以待處理狀態開始,並且可以解決:

  • 完成意味着計算成功完成。
  • 拒絕意味着計算失敗。

Promise 返回函數不應該 throw ,而是應該返回拒絕。 從承諾返回函數中拋出將迫使您同時使用} catch {.catch 使用 Promisified API 的人並不期望 Promise 會拋出。 如果您不確定異步 API 在 JS 中的工作方式 - 請先查看此答案

1. DOM 加載或其他一次性事件:

因此,創建 Promise 通常意味着指定它們何時結算 - 這意味着它們何時移動到已完成或已拒絕階段以指示數據可用(並且可以使用.then訪問)。

使用支持Promise構造函數的現代 Promise 實現,如原生 ES6 Promise:

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 中很常見。 讓我們看一下onSuccessonFail的常見情況:

function getUserData(userId, onLoad, onFail) { …

使用支持Promise構造函數的現代 Promise 實現,如原生 ES6 Promise:

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 的resolvereject方法是“可分離的”這一事實; IE。 它們綁定到 jQuery.Deferred() 的實例 並非所有庫都提供此功能。

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

使用 deferreds 您可以執行以下操作(讓我們在此示例中使用 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+ 中的本機承諾都有一個用於承諾 nodebacks 的內置方法。 例如

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

4.帶有節點樣式回調的整個庫:

這里沒有金科玉律,你一一答應他們。 但是,一些 Promise 實現允許您批量執行此操作,例如在 Bluebird 中,將 nodeback API 轉換為 Promise API 非常簡單:

Promise.promisifyAll(API);

或者使用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}), {});

筆記:

  • 當然,當您在.then處理程序中時,您不需要承諾任何事情。 .then處理程序返回一個 Promise 將使用該 Promise 的值解析或拒絕。 .then處理程序中拋出也是一種很好的做法,並且會拒絕承諾——這就是著名的承諾拋出安全性。
  • 在實際的onload情況下,您應該使用addEventListener而不是onX

今天,我可以在Node.js中使用Promise作為一個普通的 Javascript 方法。

一個簡單而基本的Promise示例(使用KISS方式):

Javascript 異步 API 代碼:

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

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

    successCallback( number / divider )

}

Promise Javascript 異步 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也可以與ES7中的async\await一起使用,以使程序流等待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

獎勵混合方法
(回調方法假設有error和result兩個參數)

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 )

     })

}

上述方法可以響應老式回調和 Promise 用法的結果。

希望這可以幫助。

在將函數轉換為 Node.JS 中的 Promise 之前

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

我認為@Benjamin 的window.onload建議不會一直有效,因為它不會檢測到它是否在加載后被調用。 我已經被那個咬過很多次了。 這是一個應該始終有效的版本:

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 包含一個新的util.promisify() API,它允許將標准 Node.js 回調樣式 API 包裝在返回 Promise 的函數中。 util.promisify()的使用示例如下所示。

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

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

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

請參閱改進對 Promises 的支持

我通常使用的一個簡單的通用函數。

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

如何使用它

  • 函數promisify接受一個帶有回調的函數:
   const cb = (result) => `The result is ${result}`;

   const sum = (a, b, cb) => {
    const result = a + b;
    cb(result); // passing args to the callback function
   }


  // using the util
  promise = promisify(sum, 3, 1, cb);
  promise.then(x => console.log(x)) // 4

您可能不是在尋找這個答案,但這將有助於了解可用工具的內部工作原理

在 Node.js 8.0.0 的候選版本中,有一個新的實用程序util.promisify (我寫過關於util.promisify的文章),它封裝了承諾任何功能的能力。

它與其他答案中建議的方法沒有太大區別,但具有作為核心方法的優勢,並且不需要額外的依賴項。

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

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

然后你有一個返回原生PromisereadFile方法。

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

您可以將 JavaScript 原生 Promise 與 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

使用普通的老式 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);
});

kriskowal 的 Q 庫包括回調到承諾的函數。 像這樣的方法:

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

可以用 Q.ninvoke 轉換

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

當您有幾個函數需要回調並且您希望它們返回一個 Promise 時,您可以使用此函數進行轉換。

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

在內置 promises 和 async 的 node v7.6+ 下:

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

在 Node.js 8 中,您可以使用此 npm 模塊即時承諾對象方法:

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

它使用util.promisifyProxies使您的對象保持不變。 記憶化也是使用 Wea ​​kMaps完成的)。 這里有些例子:

與對象:

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

您甚至可以使用本機callapply來綁定一些上下文:

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

你可以在 ES6 中使用原生 Promise ,例如處理 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
    });

}

在這個例子中,Promise 沒有失敗的理由,所以從不調用reject()

回調風格的函數總是這樣(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 是一個優秀的函數式編程庫。 在上面的代碼中,我們使用了它的apply (如 javascript function.prototype.apply )和 append(如 javascript function.prototype.push )。 因此,我們現在可以將回調樣式函數轉換為 Promise 樣式函數:

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

toPromisecheckErr函數歸berserk庫所有,它是ramda.js的函數式編程庫 fork(由我創建)。

希望這個答案對你有用。

es6-promisify將基於回調的函數轉換為基於 Promise 的函數。

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

const promisedFn = promisify(callbackedFn, args);

參考: https ://www.npmjs.com/package/es6-promisify

你可以做這樣的事情

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

我的callback函數的 Promisify 版本是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))

P函數要求回調簽名必須是callback(error,result)

下面是如何將函數(回調 API)轉換為 Promise 的實現。

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

也許已經回答了,但這是我通常的做法:

// given you've defined this `Future` fn somewhere:
const Future = fn => {return new Promise((r,t) => fn(r,t))}

// define an eventFn that takes a promise `resolver`
const eventFn = resolve => {
  // do event related closure actions here. When finally done, call `resolve()`
  something.oneventfired = e => {resolve(e)}
}

// invoke eventFn in an `async` workflowFn using `Future`
// to obtain a `promise` wrapper
const workflowFn = async () => {await Future(eventFn)}

特別是對於諸如indexedDb事件包裝器之類的東西以簡化使用。

或者您可能會發現Future的這種變體更通用

class PromiseEx extends Promise {
  resolve(v,...a) {
    this.settled = true; this.settledValue = v;
    return(this.resolve_(v,...a))
  }
  reject(v,...a) {
    this.settled = false; this.settledValue = v;
    return(this.reject_(v,...a))
  }
  static Future(fn,...args) {
    let r,t,ft = new PromiseEx((r_,t_) => {r=r_;t=t_})
    ft.resolve_ = r; ft.reject_ = t; fn(ft,...args);
    return(ft)
  }
}

死靈一點點,這個鏈接可能有用....


TLDR; 查看此答案末尾的代碼段示例


編寫/轉換可以調用的函數

cb(error,result)new Promise (...)格式


  • promiseToCB轉換並導出先前已編碼為返回承諾的現有函數
  • cbToPromise轉換和導出一個現有函數,該函數之前已編碼為使用 (error,result) 調用最后一個參數
    • 如果包裝函數提供超過 1 個結果,則結果將是一個結果數組
    • 例如cb(undefined,path,stat) ---> resolve([path,stat]) / cb(undefined,[path,stat])
  • asPromise允許您編寫一個新函數來返回一個 Promise,但它可以以任何一種方式調用
  • asCallback允許您編寫一個新函數來調用cb(err,result) ,但它可以以任何一種方式調用

示例函數

每個樣本有 2 個參數,並根據隨機數解決/拒絕/錯誤。

arg2 也可用於強制通過或失敗。 (尋找“-pass”或“-fail”)。

包裝現有功能

  • 將函數導出到當前“this”(或使用promiseToCB(function myFunc(){},newThis);


    promiseToCB(function sampleFunc1(arg1,arg2) {
        console.log("deciding:",arg1,arg2);
        return new Promise(function(resolve,reject){
       
           const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
           
           setTimeout(function(){
               if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
    
                   console.log("complete:",arg1,arg2);
                   clearTimeout(timer);
                   resolve([arg1,arg2,"all good"].join("-"));
               }
           },2000);
        
        });
    });
    
    cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {
       console.log("deciding:",arg1,arg2);
       const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
       
       setTimeout(function(){
           if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
               console.log("complete:",arg1,arg2);
               clearTimeout(timer);
               cb(undefined,[arg1,arg2,"all good"].join("-"));
           }
       },2000);
        
    },local);
    

或編寫嵌入包裝器的新函數。

     function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){
       console.log("deciding:",arg1,arg2);
       const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
       
       setTimeout(function(){
           if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
               console.log("complete:",arg1,arg2);
               clearTimeout(timer);
               resolve([arg1,arg2,"all good"].join("-"));
           }
       },2000);
        
    });}
    
    function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){
       console.log("deciding:",arg1,arg2);
       const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
       
       setTimeout(function(){
           if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
               console.log("complete:",arg1,arg2);
               clearTimeout(timer);
               cb(undefined,[arg1,arg2,"all good"].join("-"));
           }
       },2000);
        
    });}

測試上述功能的 scipt


    const local = {}; 
    promiseToCB(function sampleFunc1(arg1,arg2) {
        console.log("deciding:",arg1,arg2);
        return new Promise(function(resolve,reject){
       
           const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
           
           setTimeout(function(){
               if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
    
                   console.log("complete:",arg1,arg2);
                   clearTimeout(timer);
                   resolve([arg1,arg2,"all good"].join("-"));
               }
           },2000);
        
        });
    });
    
    cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {
       console.log("deciding:",arg1,arg2);
       const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
       
       setTimeout(function(){
           if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
               console.log("complete:",arg1,arg2);
               clearTimeout(timer);
               cb(undefined,[arg1,arg2,"all good"].join("-"));
           }
       },2000);
        
    },local);
    
    function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){
       console.log("deciding:",arg1,arg2);
       const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
       
       setTimeout(function(){
           if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
               console.log("complete:",arg1,arg2);
               clearTimeout(timer);
               resolve([arg1,arg2,"all good"].join("-"));
           }
       },2000);
        
    });}
    
    function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){
       console.log("deciding:",arg1,arg2);
       const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
       
       setTimeout(function(){
           if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
               console.log("complete:",arg1,arg2);
               clearTimeout(timer);
               cb(undefined,[arg1,arg2,"all good"].join("-"));
           }
       },2000);
        
    });}
    
    const log=console.log.bind(console),info=console.info.bind(console),error=console.error.bind(console);
    
    sampleFunc1("sample1","promise").then (log).catch(error);
    local.sampleFunc2("sample2","promise").then (log).catch(error);
    sampleFunc3("sample3","promise").then (log).catch(error);
    sampleFunc4("sample4","promise").then (log).catch(error);

    sampleFunc1("sample1","callback",info);
    local.sampleFunc2("sample2","callback",info);
    sampleFunc3("sample3","callback",info);
    sampleFunc4("sample4","callback",info);
    
    sampleFunc1("sample1","promise-pass").then (log).catch(error);
    local.sampleFunc2("sample2","promise-pass").then (log).catch(error);
    sampleFunc3("sample3","promise-pass").then (log).catch(error);
    sampleFunc4("sample4","promise-pass").then (log).catch(error);

    sampleFunc1("sample1","callback-pass",info);
    local.sampleFunc2("sample2","callback-pass",info);
    sampleFunc3("sample3","callback-pass",info);
    sampleFunc4("sample4","callback-pass",info);
    
    
    sampleFunc1("sample1","promise-fail").then (log).catch(error);
    local.sampleFunc2("sample2","promise-fail").then (log).catch(error);
    sampleFunc3("sample3","promise-fail").then (log).catch(error);
    sampleFunc4("sample4","promise-fail").then (log).catch(error);
    
    sampleFunc1("sample1","callback-fail",info);
    local.sampleFunc2("sample2","callback-fail",info);
    sampleFunc3("sample3","callback-fail",info);
    sampleFunc4("sample4","callback-fail",info);
 

 var cpArgs = Array.prototype.slice.call.bind(Array.prototype.slice); function promiseToCB (nm,fn,THIS) { if (typeof nm==='function') { THIS=fn;fn=nm;nm=fn.name; } THIS=THIS||this; const func = function () { let args = cpArgs(arguments); if (typeof args[args.length-1]==='function') { const cb = args.pop(); return fn.apply(THIS,args).then(function(r){ cb (undefined,r); }).catch(cb); } else { return fn.apply(THIS,args); } }; Object.defineProperty(func,'name',{value:nm,enumerable:false,configurable: true}); if (THIS[nm]) delete THIS[nm]; Object.defineProperty(THIS,nm,{value:func,enumerable:false,configurable: true}); return func; } function cbToPromise (nm,fn,THIS) { if (typeof nm==='function') { THIS=fn;fn=nm;nm=fn.name; } THIS=THIS||this; const func = function () { let args = cpArgs(arguments); if (typeof args[args.length-1]==='function') { return fn.apply(THIS,args); } else { return new Promise(function(resolve,reject){ args.push(function(err,result){ if (err) return reject(err); if (arguments.length==2) { return resolve(result); } return resolve(cpArgs(arguments,1)); }); fn.apply(THIS,args); }); } }; Object.defineProperty(func,'name',{value:nm,enumerable:false,configurable: true}); if (THIS[nm]) delete THIS[nm]; Object.defineProperty(THIS,nm,{value:func,enumerable:false,configurable: true}); return func; } function asPromise (args,resolver,no_err) { const cb = args[args.length-1], promise = new Promise(resolver); return (typeof cb==='function') ? promise.then(function(result){return cb(no_err,result)}).catch(cb) : promise; } function asCallback (args,wrap,no_err) { const cb = args[args.length-1], promise=new Promise(function resolver(resolve,reject) { return wrap (function (err,result) { if (err) return reject(err); resolve(result); }); }); return (typeof cb==='function') ? promise.then(function(result){return cb(no_err,result)}).catch(cb) : promise; } function cbPromiseTest(){ /*global sampleFunc1,sampleFunc2*/ const local = {}; promiseToCB(function sampleFunc1(arg1,arg2) { console.log("deciding:",arg1,arg2); return new Promise(function(resolve,reject){ const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000); setTimeout(function(){ if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) { console.log("complete:",arg1,arg2); clearTimeout(timer); resolve([arg1,arg2,"all good"].join("-")); } },2000); }); }); cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) { console.log("deciding:",arg1,arg2); const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000); setTimeout(function(){ if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) { console.log("complete:",arg1,arg2); clearTimeout(timer); cb(undefined,[arg1,arg2,"all good"].join("-")); } },2000); },local); function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){ console.log("deciding:",arg1,arg2); const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000); setTimeout(function(){ if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) { console.log("complete:",arg1,arg2); clearTimeout(timer); resolve([arg1,arg2,"all good"].join("-")); } },2000); });} function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){ console.log("deciding:",arg1,arg2); const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000); setTimeout(function(){ if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) { console.log("complete:",arg1,arg2); clearTimeout(timer); cb(undefined,[arg1,arg2,"all good"].join("-")); } },2000); });} const log=console.log.bind(console),info=console.info.bind(console),error=console.error.bind(console); sampleFunc1("sample1","promise").then (log).catch(error); local.sampleFunc2("sample2","promise").then (log).catch(error); sampleFunc3("sample3","promise").then (log).catch(error); sampleFunc4("sample4","promise").then (log).catch(error); sampleFunc1("sample1","callback",info); local.sampleFunc2("sample2","callback",info); sampleFunc3("sample3","callback",info); sampleFunc4("sample4","callback",info); sampleFunc1("sample1","promise-pass").then (log).catch(error); local.sampleFunc2("sample2","promise-pass").then (log).catch(error); sampleFunc3("sample3","promise-pass").then (log).catch(error); sampleFunc4("sample4","promise-pass").then (log).catch(error); sampleFunc1("sample1","callback-pass",info); local.sampleFunc2("sample2","callback-pass",info); sampleFunc3("sample3","callback-pass",info); sampleFunc4("sample4","callback-pass",info); sampleFunc1("sample1","promise-fail").then (log).catch(error); local.sampleFunc2("sample2","promise-fail").then (log).catch(error); sampleFunc3("sample3","promise-fail").then (log).catch(error); sampleFunc4("sample4","promise-fail").then (log).catch(error); sampleFunc1("sample1","callback-fail",info); local.sampleFunc2("sample2","callback-fail",info); sampleFunc3("sample3","callback-fail",info); sampleFunc4("sample4","callback-fail",info); } cbPromiseTest();

承諾總是有resolvereject 當您編寫異步包裝器時,只需調用 resolve 即可。

您可以為幾乎任何采用回調的函數編寫包裝函數,如下所示:

const myAsyncWrapper = (...params) =>
  new Promise((resolve, reject) => 
    someFunctionWithCallback(...params, (error, response) =>
      error ? reject(error) : resolve(response)
    )
  );

您可以進一步編寫回調到 Promise 的轉換函數:

const promisify =
  (functionWithCallback) =>
  (...params) =>
    new Promise((resolve, reject) =>
      functionWithCallback(...params, (error, response) =>
        error ? reject(error) : resolve(response)
      )
    );

在使用較舊的庫或 SDK 時,包裝函數的概念特別有用。 例如,考慮 Facebook Graph API 的 JavaScript SDK,它使用類似的回調結構來發出 API 請求。

FB.api(apiURL, options, function (request) {
  if (request.error || !request) return;
  // handle request
});

在現代應用程序中,使用基於 Promise 的 API 更為有用。 如果您只使用一次或兩次函數,最好單獨承諾響應:

// in an async function
const response = await new Promise((resolve, reject) =>
  FB.api(apiURL, (res) => (res?.error ? reject(res?.error) : resolve(res)))
);

如果你經常使用這個函數,你可以使用相同的包裝器概念來編寫一個函數,如下所示:

const apiWrapper = (...params) =>
  new Promise((resolve, reject) => 
    FB.api(...params, (res) => (res?.error ? reject(res?.error) : resolve(res)))
  );

盡管承諾者有時很棒,但它們不適用於像這樣的特定情況。 在這種情況下,在 Github 上尋找現代包裝器,或者像這樣編寫自己的包裝器。

由於我們事先知道基於回調的函數的特性,我們可以創建一個函數,將基於回調的函數轉換為返回 Promise 的等效函數。

  • 回調是函數的最后一個參數

  • 如果有錯誤,它總是第一個參數傳遞給回調

  • 錯誤后的任何返回值都傳遞給回調

     function promisify(yourCallbackApi) { return function promisified(...args) { return new Promise((resolve, reject) => { // newArgs=[..args,callback] const newArgs = [ ...args, function (err, result) { if (err) { return reject(err); } resolve(result); }, ]; // invoke yourCallbackApi with the new list of arguments yourCallbackApi(...newArgs); }); }; }

我創建了可以重用的通用函數。

       const wrapIntoPromise: (fnToExecute) => {
            return new Promise((resolve, reject) => {
                return fnToExecute(resolve, reject);
            });
        }

        //example
        async function run() {
           await wrapIntoPromise((resolve, reject) => { 
               /*execute any function here with callbacks etc
                when done just call return resolve(valueToReturn)
               */ 
               return backup({
                  callback: function() { return resolve()}
               });

           });
        }

好像晚了 5 年,但我想在這里發布我的 promesify 版本,它從回調 API 中獲取函數並將它們轉換為 Promise

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

在這里看看這個非常簡單的版本: https ://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM