es6+最佳入门实践(8)

8.Promise

8.1.什么是异步?

要理解异步,首先,从同步代码开始说

alert(1)
alert(2)

像上面的代码,执行顺序是从上到下,先后弹出1和2,这种代码叫做同步代码

alert(0)
setTimeout(function () {
    alert(1);
}, 2000);
setTimeout(function () {
    alert(2)
}, 1000);

alert(3)

上面代码的弹出顺序是 0 3 2 1 ,像这种不按从上到下依次执行的代码叫做异步代码,其实还有很多类似的异步代码,例如:ajax请求

ajax({
    type:'get',
    url: 'http://xxx.com/xxx',
    success: function(result){}
})
console.log(111)

异步回调嵌套问题

setTimeout(function () {
    alert(1)
    setTimeout(function () {
        alert(2)
        setTimeout(function () {
            alert(3)
        }, 10)
    }, 100)
}, 1000)

8.2.什么是Promise?

Promise是ES6中的异步编程解决方案,在代码中表现为一个对象,可以通过构造函数Promise来实例化,有了Promise对象,可以将异步操作以同步的流程表达出来,避免了回调地狱(回调函数层层嵌套)

直观的去看看Promise到底是什么

console.dir(Promise)
71144-c180itl9kq9.png

这样一看就很明白了,Promise是一个构造函数,它身上有几个方法,例如:reject、resolve、catch、all、race等方法就是我们常用的一些方法,还有then方法在它的原型上,也是非常常用的,后面我们会详细讲解这些方法

既然是构造函数,那么我们就可以使用new来调用一下,简单的使用

let p = new Promise((resolve, reject) => {
       setTimeout(()=>{
           //代码执行完成
           console.log('代码执行完成');
           resolve()
       }, 1000)
   })

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),上面代码中传入的函数有两个参数,resolve和reject,这两个参数都是函数块,用于回调执行,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected,只有这两个结果可以去操作Promise的状态,其他任何操作都不能更改这个状态,这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。在初学阶段你可以简单的理解为resole就是异步执行成功后被调用的函数,reject是异步执行失败后调用的函数

注意: 上面代码中我们只是去new Promise() 得到一个实例,但是发现异步代码中的语句在1秒后被执行了,也就是说只要new Promise(), 那么promise里面的函数就会被立即执行,这是非常重要的一个细节,我们应该做到需要的时候去执行,而不是不管什么情况都去执行,因此,我们通常把上面的代码包到一个函数中去,需要的时候,调用一下函数就可以了

function AsyncFn() {
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            //代码执行完成
            console.log('代码执行完成');
            resolve()
        }, 1000)
    });
    return p;
}

函数封装好后到底有什么用?在什么情况下用?resolve拿来做什么? 带着这些疑问,我们继续往下讲

在Promise的原型上有一个叫做then的方法,它的作用是为 Promise 实例添加状态改变时的回调函数,我们首先来看看then方法的位置

console.dir(Promise)
45335-xzw8449cb3.png

下面我们来具体使用这个then方法

function AsyncFn() {
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            //代码执行完成
            console.log('代码执行完成');
            resolve()
        }, 1000)
    });
    return p;
}
AsyncFn().then(function () {
    alert('异步代码执行完成后,该我执行了')
})

代码写到这里,我们已经能看出Promise的作用了,它其实已经可以把原来回调函数函数写到异步代码里的这种写法改变了,它已经把回调函数函数分离出来了,在异步代码执行完成后,通过链式调用的方式来执行回调函数函数,如果仅仅是向上面的代码只执行一次回调函数可能看不出Promise带来的好处,下面我们来看更复杂的代码


    function AsyncFn1() {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步代码1代码执行完成');
                resolve()
            }, 1000)
        });
        return p;
    }

    function AsyncFn2() {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                //代码执行完成
                console.log('异步代码2执行完成');
                resolve()
            }, 3000)
        });
        return p;
    }

    function AsyncFn3() {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                //代码执行完成
                console.log('异步代码3执行完成');
                resolve()
            }, 2000)
        });
        return p;
    }

需求:AsyncFn3 是依赖于AsyncFn2的 AsyncFn2是依赖于AsyncFn1的,这就要求AsyncFn1执行完成后再执行AsyncFn2,AsyncFn2执行完成后执行AsyncFn3,这个时候怎么写?

function AsyncFn1() {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('异步代码1代码执行完成');
                resolve()
            }, 1000)
        });
        return p;
    }

    function AsyncFn2() {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                //代码执行完成
                console.log('异步代码2执行完成');
                resolve()
            }, 3000)
        });
        return p;
    }

    function AsyncFn3() {
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                //代码执行完成
                console.log('异步代码3执行完成');
                resolve()
            }, 2000)
        });
        return p;
    }

    //需求:AsyncFn3 是依赖于AsyncFn2的 AsyncFn2是依赖于AsyncFn1的,这就要求AsyncFn1执行完成后
    // 再执行AsyncFn2,AsyncFn2执行完成后执行AsyncFn3,这个时候怎么写?
    AsyncFn1().then(()=>{
        alert('异步代码1执行完成后,该我执行了');
        //上面代码执行完成后,返回一个Promise对象

        return AsyncFn2()
    }).then(()=>{
         alert('异步代码2执行完成后,该我执行了');
         return AsyncFn3()
    }).then(()=>{
        alert('异步代码3执行完成后,该我执行了');
    })

到底为止,Promise的作用已经差不多可以理解了,它是ES6中的异步解决方案,可以将异步的代码以同步的形式表现出来,避免回调函数函数嵌套

如果理解了resolve的话,那么理解reject就比较容易了,它是异步代码执行失败后执行的回调函数。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调

 let oBtn = document.getElementById('btn');
    let oResult = document.getElementById('result');

    oBtn.onclick = () => {
        AsyncGetData().then(() => {
            oResult.innerHTML = '执行成功,获取到了数据。。。'
        }, () => {
            oResult.innerHTML = '<span style="color: red">执行失败,没有获取到数据。。。</span>'
        })
    };

    function AsyncGetData() {
        let num = Math.random() * 20;
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if( num > 10){
                    resolve();
                }else{
                    reject();
                }
            })
        })
    }

8.3.实例练习

1.异步加载图片

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="btn">获取图片</button>

<script>
    const arr = [
        'http://edu.nodeing.com/files/system/block_picture_1516366943.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516369814.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516977533.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516973104.jpg?version=8.2.15'
    ];

    function AsyncLoadImg(url) {
        let p = new Promise((resolve, reject) => {
            let oImg = new Image();
            oImg.src = url;
            oImg.onload = () => {
                resolve(oImg)
            };
            oImg.onerror = () => {
                let error = new Error('图片加载失败');
                reject(error)
            }
        });

        return p;
    }

    let oBtn = document.getElementById('btn');
    oBtn.onclick = () => {
        //无序加载
        // for (let i = 0; i < arr.length; i++) {
        //     AsyncLoadImg(arr[i]).then(function (oResult) {
        //         document.body.appendChild(oResult);
        //     })
        // }

        //按顺序加载
        AsyncLoadImg(arr[0]).then((oResult) => {
            oResult.title = '图片1';
            document.body.appendChild(oResult);
            return AsyncLoadImg(arr[1])
        }).then((oResult) => {
            oResult.title = '图片2';
            document.body.appendChild(oResult);
            return AsyncLoadImg(arr[2])
        }).then((oResult) => {
            oResult.title = '图片3';
            document.body.appendChild(oResult);
            return AsyncLoadImg(arr[3])
        }).then((oResult) => {
            oResult.title = '图片4';
            document.body.appendChild(oResult);
        })

    }

</script>
</body>
</html>

8.4.Promise相关方法

1.catch的用法

catch方法和then的第二个参数作用差不多,都是用来指定异步执行失败后的回调函数函数的,不过,它还有一个功能就是如果在resolve中抛出错误,不会阻塞执行,而是可以在catch中捕获到错误

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="btn">模拟获取数据</button>
<p id="result"></p>
<script>
    let oBtn = document.getElementById('btn');
    let oResult = document.getElementById('result');

    oBtn.onclick = () => {
        AsyncGetData().then(() => {
            oResult.innerHTML = '执行成功,获取到了数据。。。'
            throw new Error('这里报错了')
        }).catch((e) => {
            console.log(e)
        })
    };

    function AsyncGetData() {
        let num = Math.random() * 20;
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if( num > 10){
                    resolve();
                }else{
                    reject();
                }
            })
        })
    }
</script>
</body>
</html>

2.all方法

all方法中传入一个数组,里面是多个Promise实例,只有当所有的Promise实例的状态变为fulfilled的时候,整体的状态才会变成fulfilled,这个时候每个Promise的实例返回的值会组成一个数组传给回调函数,如果整个数组中的Promise实例中有一个的状态是rejected,那么整体的状态都会是rejected,这个时候,第一个rejected实例的返回值会传给回调函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="btn">获取图片</button>

<script>
    const arr = [
        'http://edu.nodeing.com/files/system/block_picture_1516366943.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516369814.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516977533.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516973104.jpg?version=8.2.15'
    ];

    function AsyncLoadImg(url) {
        let p = new Promise((resolve, reject) => {
            let oImg = new Image();
            oImg.src = url;
            oImg.onload = () => {
                resolve(oImg)
            };
            oImg.onerror = () => {
                let error = new Error('图片加载失败');
                reject(error)
            }
        });

        return p;
    }

    let oBtn = document.getElementById('btn');
    oBtn.onclick = () => {

        Promise.all([AsyncLoadImg(arr[0]),AsyncLoadImg(arr[1]),AsyncLoadImg(arr[2]),AsyncLoadImg(arr[3])])
            .then((result) => {
                // console.log(result)
                for(let i in result){
                    document.body.appendChild(result[i]);
                }
            })
    }

</script>
</body>
</html>

all方法通常适用于先加载资源,再执行操作的场景,例如:前面我们写的贪吃蛇项目,首先去加载地图、图片、以及声音等这些资源,等加载成功后再执行初始化

3.race方法

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="btn">获取图片</button>

<script>
    const arr = [
        'http://edus.nodeing.com/files/system/block_picture_1516366943.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516369814.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516977533.jpg?version=8.2.15',
        'http://edu.nodeing.com/files/system/block_picture_1516973104.jpg?version=8.2.15'
    ];

    function AsyncLoadImg(url) {
        let p = new Promise((resolve, reject) => {
            let oImg = new Image();
            oImg.src = url;
            oImg.onload = () => {
                resolve(oImg)
            };
        });

        return p;
    }
    //图片超时测试
    function timeOut() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject(new Error('图片超时'));
            }, 1000)
        })
    }

    let oBtn = document.getElementById('btn');
    oBtn.onclick = () => {

        let p = Promise.race([AsyncLoadImg(arr[0]), timeOut()])
            .then((result) => {
               document.body.appendChild(result)
            }).catch((err) => {
                console.log(err);
            })
    }

</script>
</body>
</html>

视频教程地址:http://edu.nodeing.com/course/50

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,703评论 1 56
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,352评论 0 19
  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,167评论 0 16
  • 一、Promise的含义 Promise在JavaScript语言中早有实现,ES6将其写进了语言标准,统一了用法...
    Alex灌汤猫阅读 820评论 0 2
  • 暖身又暖心。 放很多姜丝,油里煸香,然后炒已经切丝的白菜,,炒到白菜软了,放高汤,我这里用的是家里炖的棒骨汤,羊杂...
    左家半亩闲田阅读 356评论 0 0