最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 给初学者的 JavaScript Promise 教程 | Scotch

    正文概述 掘金(众成翻译)   2021-02-02   427

    译者:cherryvenus

    原文链接

    Code

    Demo

    Javascript Promises 不难。然而,许多人刚开始接触这个概念的时候,觉得有些难以理解。因此,我写下了我是如何理解 Promise 的,用一个通俗易懂的方法。

    理解 Promise

    Promise 简介:

    ""假设你是一个宝宝. 你的妈妈承诺(Promise)你,下个礼拜她会给你一台新手机。""

    你_不知道_,下个礼拜你是否会拿到手机。你的妈妈可以_真的给你买_一个全新的手机,或者_放你鸽子_,也有可能如果她不开心:(了就扣下了手机。

    这个就是承诺(Promise)。一个 Promise 有3个状态。他们分别是:

    1. Promise 是待定的(pending): 你不知道你下个礼拜能不能拿到手机。
    2. Promise 是已解决的(resolved):你的妈妈真的给你买了一个全新的手机。
    3. Promise 是被拒绝的(rejected): 因为你妈妈不开心所以不给你手机了。

    创建一个 Promise

    让我们将这个转化为 JavaScript。

    /* ES5 */
    var isMomHappy = false;
    // Promise
    var willIGetNewPhone = new Promise(
        function (resolve, reject) {
            if (isMomHappy) {
                var phone = {
                    brand: 'Samsung',
                    color: 'black'
                };
                resolve(phone); // 完成了
            } else {
                var reason = new Error('妈妈不开心');
                reject(reason); // reject
            }
    
        }
    ); 
    

    代码本身颇具表现力。

    1. 我们用一个布尔值 isMomHappy,来定义妈妈是否开心。

    2. 我们有一个命名为willIGetNewPhone的 Promise 。这个 Promise 可以是 已完成的(resolved) (如果妈妈给你买了一个新手机)或者 被拒绝的(rejected) (妈妈不开心,她没有给你买一个)。

    3. 这里有一个标准的语法来新建一个 Promise,参考MDN 文档,一个 promise 语法看上去像这样。

    // promise 语法看上去像这样
    new Promise(/* executor*/ function (resolve, reject) { ... } );
    
    1. 你需要记住的是,当结果是正确的,在你的 promise 中调用 resolve(正确的值)。在我们的例子中,如果妈妈很开心,我们就会拿到手机。因此,我们调用 resolve 函数和 phone 这个变量。如果我们的妈妈不开心,我们会调用 reject 函数和一个理由(reason)reject(reason)

    使用 Promise

    现在,我们有一个 Promise。来看看怎么使用它:

    /* ES5 */
    ...
    // 调用我们的 Promise
    var askMom = function () {
        willIGetNewPhone
            .then(function (fulfilled) {
                // 太好啦, 你获得了一个新手机
                console.log(fulfilled);
             // output: { brand: 'Samsung', color: 'black' }
            })
            .catch(function (error) {
                // 好不幸,你妈妈没买手机
                console.log(error.message);
             // output: '妈妈不开心'
            });
    };
    askMom();
    
    1. 我们有一个名为 askMom 的函数。在这个函数中,我们会使用 Promise willIGetNewPhone

    2. 一旦 Promise 被解决(resolved)或者被拒(rejected),我门希望采取些措施。我们用 .then.catch 来实现。

    3. 在我们的例子中,.then 之中有个 function(fulfilled) { ... }fulfilled 是什么?fulfilled 就是是你传入 Promise 的 resolve(your_success_value).因此,在我们例子中就是 phone

    4. 我们在 .catch 中有 function(error){ ... }error 是什么?正如你猜测的,error 正是你传入 Promise 中的 reject(your_fail_value) 。因此,在我们的例子中就是 reason

    让我们看看例子运行之后的结果吧!

    Demo: jsbin.com/nifocu/1/ed…

    给初学者的 JavaScript Promise 教程 | Scotch

    串联 Promise

    Promiss 是可串联的。

    也就是说,你,宝宝,承诺(Promise)你的小伙伴,当你妈妈给你买了手机,你就会给他们看新手机。

    这就是另一个 Promise 啦。我们来写一个!

    // 简略
    ...
    // 第二个 promise
    var showOff = function (phone) {
        return new Promise(
            function (resolve, reject) {
                var message = 'hey 伙计,我有个新 ' +
                    phone.color + ' ' + phone.brand + '手机';
                resolve(message);
            }
        );
    };
    

    说明:

    • 在这个例子中,你可能意识到我们没有调用 reject。因为这个是可选的参数。

    • 我们可以简化这个样例就像用 Promise.resolve 代替。

    // 简略
    ...
    
    // 第二个 promise
    var showOff = function (phone) {
        var message = 'hey 伙计,我有个新 ' +
                    phone.color + ' ' + phone.brand + ' 手机';
        return Promise.resolve(message);
    };
    

    让我们串联 Promise。你,宝宝只能在willIGetNewPhone Promise 实现之后,才能开始 showOff Promise。

    ...
    // 调用 Promise
    var askMom = function () {
        willIGetNewPhone
        .then(showOff) // 在这里串联
        .then(function (fulfilled) {
                console.log(fulfilled);
             // output: 'Hey 伙计, 我有一个新的黑色三星手机。'
            })
            .catch(function (error) {
                // 好不幸,你妈妈没买手机
                console.log(error.message);
             // output: '妈妈不开心'
            });
    };
    

    串联 Promise 简单吧!

    Promises 是异步的

    Promise 是异步的。让我们在调用 Promise 之前和之后各打印一个信息。

    // 调用我们的Ppromise
    var askMom = function () {
        console.log('询问妈妈之前'); // 运行之前打印
        willIGetNewPhone
            .then(showOff)
            .then(function (fulfilled) {
                console.log(fulfilled);
            })
            .catch(function (error) {
                console.log(error.message);
            });
        console.log('询问妈妈之后'); // 运行之后打印
    } 
    

    预计的输出序列是怎么样的?也许你预计是这样的

    1\. 询问妈妈之前
    2\. Hey 伙计, 我有一个新的黑色三星手机。
    3\. 询问妈妈之后
    

    然而, 真实的输出顺序是这样的:

    1\. 询问妈妈之前
    2\. 询问妈妈之后
    3\. Hey 朋友, 我有一个新的黑色三星手机。
    

    给初学者的 JavaScript Promise 教程 | Scotch

    为什么? 因为生命 (或者 JavaScript) 不等人。

    宝宝在玩的时候等待着妈妈的承诺(promise) (新手机).不是吗? 这个我们称之为 异步(asynchronous), 代码不会因为阻塞或等待结果而不运行. 任何想等待 Promise 之后再运行的, 你需要把他们放入 .then.

    ES5, ES6/2015, ES7/Next 中的 promise

    ES5 - 大多数浏览器

    demo代码在 ES5 的环境(所有主流浏览器+NodeJs)中是可以运行,如果你包含了Bluebird Promise 库。这是因为 ES5 不支持直接调用 Promise 。另一个有名的 Promise 库是 Kris Kowal 的Q。

    ES6 / ES2015 - 现代浏览器, NodeJs v6

    demo代码可以直接调用,因为ES6支持本地 Promise。外加,和 ES6 函数 fat arrow =>,以及 constlet 搭配使用,我们可以进一步简化代码`。

    这里是 ES6 代码的例子

    /* ES6 */
    const isMomHappy = true;
    
    // Promise
    const willIGetNewPhone = new Promise(
        (resolve, reject) => { // fat arrow
            if (isMomHappy) {
                const phone = {
                    brand: 'Samsung',
                    color: 'black'
                };
                resolve(phone);
            } else {
                const reason = new Error('mom is not happy');
                reject(reason);
            }
    
        }
    );
    
    const showOff = function (phone) {
        const message = 'Hey 伙计, 我有个一个新' +
                    phone.color + ' ' + phone.brand + '手机';
        return Promise.resolve(message);
    };
    
    // 调用我们的promise
    const askMom = function () {
        willIGetNewPhone
            .then(showOff)
            .then(fulfilled => console.log(fulfilled)) // fat arrow
            .catch(error => console.log(error.message)); // fat arrow
    };
    
    askMom();
    

    注意,所有的var都用 const代替。所有的 function(resolve, reject) 都简化为(resolve, reject) =>。 这些改变有许多好处。阅读更多:

    • JavaScript ES6 的变量声明, let 和 const

    • Javascript ES6 箭头函数介绍

    ES7 - 异步等待让语法看上去更整洁

    ES7 引入 asyncawait 语法。这让异步语法看上去更整洁和易于理解,而不用.then.catch

    用 ES7 的语法重写例子

    /* ES7 */
    const isMomHappy = true;
    
    // Promise
    const willIGetNewPhone = new Promise(
        (resolve, reject) => {
            if (isMomHappy) {
                const phone = {
                    brand: 'Samsung',
                    color: 'black'
                };
                resolve(phone);
            } else {
                const reason = new Error('妈妈不开心');
                reject(reason);
            }
    
        }
    );
    
    // 2nd promise
    async function showOff(phone) {
        return new Promise(
            (resolve, reject) => {
                var message = 'Hey 伙计, 我有一个新' +
                    phone.color + ' ' + phone.brand + '手机';
    
                resolve(message);
            }
        );
    };
    
    // 调用 Promise
    async function askMom() {
        try {
            console.log('before asking Mom');
    
            let phone = await willIGetNewPhone;
            let message = await showOff(phone);
    
            console.log(message);
            console.log('after asking mom');
        }
        catch (error) {
            console.log(error.message);
        }
    }
    
    (async () => {
        await askMom();
    })();
    
    1. 每当你需要在函数中返回一个 Promise 的时候,你要在函数之前添加 async

    E.g. async function showOff(phone)

    1. 当你需要调用一个 promise,你需要在此之前添加 await

    E.g. let phone = await willIGetNewPhone; and let message = await showOff(phone);.

    1. 使用 try { ... } catch(error) { ... } 来捕捉 Promise 错误,被拒绝的 promise

    为什么用 Promise 以及何时用他们?

    为什么你需要 promise ?在 promise 之前我们是如何的?在回答这些问题之前,让我们回到基本原理。

    普通函数 vs 异步函数

    让我们看看这两个例子,他们都执行两个数字相加,一个用普通函数相加,一个用远程方法相加。

    普通函数相加两个数字

    // 正常相加数字
    
    function add (num1, num2) {
        return num1 + num2;
    }
    
    const result = add(1, 2); // you get result = 3 immediately 
    
    异步函数相加两个数字
    // 远程相加数字
    
    // 调用api获得结果
    const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
    // you get result  = ""undefined""
    

    如果你用普通函数相加两数字,你会马上获得结果。然而如果你发出一个远程调用来获得结果,那么你就需要等待,你不能马上得到结果。

    或者这样说,你不知道会不会得到结果,因为服务器可能会性能下降,响应慢等等。你不希望因为等待着结果,让整个进程都被堵住。

    调用API,下载文件,读取文件一些平时你会执行的异步操作。

    Pomise 出现之前的世界: 回调(Callback)

    我们一定要用 Prmoise 来做异步回调吗?不是的。优先于 promise ,我们用回调(callback)。回调(callback)仅仅是个你调用的函数,当你获得返回结果的时候。让我们修改之前的例子来获得一个回调。

    // 远程相加两数字
    // 调用API获得结果
    
    function addAsync (num1, num2, callback) {
        //使用有名的 jQuery getJSON 的回调 API
        return $.getJSON('http://www.example.com', {
            num1: num1,
            num2: num2
        }, callback);
    }
    
    addAsync(1, 2, success => {
        // callback
        const result = success; // 这里你得到 result = 3
    }); 
    

    这个语法看上去OK,为什么我们之后需要用 Promise ?

    如果你想做一系列的异步操作怎么办??

    比如说,不同于一次仅仅相加两个数字,我们希望加3次。用普通的函数,我们这么做:

    // 普通相加两个数字
    
    let resultA, resultB, resultC;
    
     function add (num1, num2) {
        return num1 + num2;
    }
    
    resultA = add(1, 2); // 你马上获得 resultA = 3
    resultB = add(resultA, 3); // 你马上获得 esultB = 6
    resultC = add(resultB, 4); // 你马上获得  resultC = 10
    
    console.log('total' + resultC);
    console.log(resultA, resultB, resultC);
    

    用了回调之后,这个看上去是什么样的?

    // 远程相加两个数字
    // 调用API获得结果
    
    let resultA, resultB, resultC;
    
    function addAsync (num1, num2, callback) {
        // use the famous jQuery getJSON callback API
        return $.getJSON('http://www.example.com', {
            num1: num1,
            num2: num2
        }, callback);
    }
    
    addAsync(1, 2, success => {
        // callback 1
        resultA = success; // you get result = 3 here
    
        addAsync(resultA, 3, success => {
            // callback 2
            resultB = success; // you get result = 6 here
    
            addAsync(resultB, 4, success => {
                // callback 3
                resultC = success; // you get result = 10 here
    
                console.log('total' + resultC);
                console.log(resultA, resultB, resultC);
            });
        });
    }); 
    

    Demo: jsbin.com/barimo/edit…

    这个语法很不友好。更贴切地说,这个看上去像金字塔,人们经常称呼为 ""回调地狱"",因为一个回调嵌在另一个回调之中。想象你有10个回调,你的代码嵌套了10次!

    逃离回调地狱

    Promise 来拯救你了。让我们看一下相同例子的 Promise 的版本。

    // 使用observables,远程相加连个数字
    
    let resultA, resultB, resultC;
    
    function addAsync(num1, num2) {
        // 使用 ES6 抓取API, 这个返回一个 promise
        return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
            .then(x => x.json());
    }
    
    addAsync(1, 2)
        .then(success => {
            resultA = success;
            return resultA;
        })
        .then(success => addAsync(success, 3))
        .then(success => {
            resultB = success;
            return resultB;
        })
        .then(success => addAsync(success, 4))
        .then(success => {
            resultC = success;
            return resultC;
        })
        .then(success => {
            console.log('total: ' + success)
            console.log(resultA, resultB, resultC)
        }); 
    

    Demo: jsbin.com/qafane/edit…

    和 promise 我们用.then让回调在统一层次上。在某种程度上,这个看上去更加干净了,因为没有回调嵌套。当然,用 ES7 async 语法,我们可以加强这个例子,但是我把它留给你。:)

    新鲜出炉的属性: 监控属性(observables)

    在你搞定 Promise 之前,有样叫做Observables的东西出现了,这让你处理异步数据更加简单。

    Promises 和 observables 之间几点关键的差异:

    • Observables 是可以取消的
    • Observables 是懒惰的

    不要怕,让我们看看用 Observables 写相同demo。在这个例子中,我为 Observables 使用 RxJS。

    let Observable = Rx.Observable;
    let resultA, resultB, resultC;
    
    function addAsync(num1, num2) {
        // 用 ES6 抓取 API, 返回一个 promise
        const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
            .then(x => x.json());
    
        return Observable.fromPromise(promise);
    }
    
    addAsync(1,2)
      .do(x => resultA = x)
      .flatMap(x => addAsync(x, 3))
      .do(x => resultB = x)
      .flatMap(x => addAsync(x, 4))
      .do(x => resultC = x)
      .subscribe(x => {
        console.log('total: ' + x)
        console.log(resultA, resultB, resultC)
      }); 
    

    Demo: jsbin.com/dosaviwalu/…

    注意:

    • Observable.fromPromise 将一个 promise 转化为 Observable 流,
    • .do.flatMap 是 observables 提供的操作符中的两个。
    • 流是懒惰的。当我们 .subscribe 它的时候,addAsync 才会运行。

    Observables 可以简单地处理恶心的东西。比如,delay 添加 3 seconds 函数,只有一行代码或者充实,因此你可以重试一个调用一定的次数。

    ...
    
    addAsync(1,2)
      .delay(3000) // delay 3 seconds
      .do(x => resultA = x)
      ... 
    

    好啦,让我们在未来的文章再讨论 observable 吧!

    总结

    你有没有熟悉了 callback 和 Promise 呢? 理解他们使用他们。不要担心 Observable, 也就这样。三个关键因素对你的开发来说,视情况而定。

    这里是所有的 妈妈承诺买手机 demo的代码

    • Demo (ES5): jsbin.com/habuwuyeqo/…

    • Demo (ES6): jsbin.com/cezedu/edit…

    • Demo (ES7): goo.gl/U3fPmh

    • Github example (ES7): github.com/chybie/js-a…

    就这样!希望这篇文章能够铲除你征服 JavaScript Promise 路途上的荆棘、。快乐码农~


    起源地下载网 » 给初学者的 JavaScript Promise 教程 | Scotch

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元