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)

这样一看就很明白了,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)

下面我们来具体使用这个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

原文地址:https://www.cnblogs.com/dadifeihong/p/10358127.html

时间: 2024-10-05 05:07:49

es6+最佳入门实践(8)的相关文章

es6+最佳入门实践(10)

10.Generator 10.1.Generator是什么? Generator函数是ES6提供的一种异步编程解决方案.在它的内部封装了多个状态,因此,又可以理解为一种状态机,执行Generator函数后返回一个迭代器对象,使用这个迭代器对象可以遍历出Generator函数内部的状态 Generator函数和传统函数的不同点有:1 函数定义的时候,function关键字后面加"*", 2 内部使用yield关键字定义内部状态 function* HelloGenerator() {

es6+最佳入门实践(1)

1.let和const 1.1.let和块级作用域 在es5中,js的作用域分为全局作用域和局部作用域,通常是用函数来区分的,函数内部属于局部作用域,在es6中新增了块级作用域的概念,使用{}括起来的区域是一个块级作用域 { var a = 10 } // 输出10 console.log(a) 如果上述代码中定义变量的时候使用let,在外面使用变量a就会报错 { let a = 10 } // 以下输出会报错 console.log(a) es6中新增了块级作用域,let定义的a只能在当前的{

es6+最佳入门实践(12)

12.class基础用法和继承 12.1.class基础语法 在es5中,面向对象我们通常写成这样 function Person(name,age) { this.name = name; this.age = age; } Person.prototype.showName = function () { console.log(this.name); }; let p = new Person("xiaoqiang", 10); p.showName(); 上面这种写法与传统的面

es6+最佳入门实践(6)

6.Symbol用法 6.1.什么是Symbol? Symbol是es6中一种新增加的数据类型,它表示独一无二的值.es5中我们把数据类型分为基本数据类型(字符串.数字.布尔.undefined.null)和引用数据类型(Object),在es6中新增的Symbol数据类型划分到基本数据类型 为什么会有这样一种数据类型呢? //别人给了你一个定义好的对象 var obj = { name: "xiaoqiang", showName: function(){alert(1)} } //

es6+最佳入门实践(9)

9.Iterator和for...of 9.1.Iterator是什么? Iterator又叫做迭代器,它是一种接口,为各种不同的数据结构提供统一的访问机制.这里说的接口可以形象的理解为USB接口,有了这个接口可以做不同的事情,在编程中所说的接口最终都是要通过一段代码块来实现这个接口功能的.而Iterator接口提供的统一访问机制主要表现为遍历操作,任何数据类型只要具有Iterator接口,就可以完成遍历操作(遍历操作指依次处理该数据结构的所有成员),总结起来就是说我们需要一种统一的机制来依次处

es6+最佳入门实践(7)

7.set和map数据结构 7.1.什么是set? Set就是集合,集合是由一组无序且唯一的项组成,在es6中新增了set这种数据结构,有点类似于数组,但是它的元素是唯一的,没有重复 let st = new Set([1,2,2,3,3,4]) console.log(st) // [1,2,3,4] Set的size属性可以知道set中有多少元素,类似于数组的length属性 let st = new Set([1,2,2,3,3,4]) console.log(st.size); //4

es6+最佳入门实践(5)

5.对象扩展 5.1.对象简写 在es5中,有这样一种写法 var name = "xiaoqiang"; var age = 12; var obj = { name : name, age : age } 在es6中,我们可以简写成这样一种形式 let name = "xiaoqiang"; let age = 12; let obj = { name, age } 以上只是属性的简写,如果有方法应该怎么写呢?首先我们来回顾一下es5中的写法 var obj =

es6+最佳入门实践(3)

3.数组扩展 3.1.扩展运算符 扩展运算符用三个点(...)表示,从字面上理解,它的功能就是把数组扩展开来,具体形式如下: let arr = [1, 2, 3]; console.log(...arr); //打印结果 1 2 3 //等价于 console.log(1,2,3); 从上面代码中,我们可以看出...arr展开后的形式是这样的 1,2,3,用逗号隔开的参数序列 在函数调用的时候,可以用这个扩展运算符把数组里面的元素展开,分别传给函数的形参 let arr = [4, 5, 6]

es6+最佳入门实践(4)

4.函数扩展 4.1.参数默认值 默认参数就是当用户没有传值的时候函数内部默认使用的值,在es5中我们通过逻辑运算符||来实现 function Fn(a, b) { b = b || "nodeing"; return a + b } console.log(Fn("hello")) 这样写有一个缺点就是当我传入一个空字符串的时候,返回的结果并不是我想要的结果,正确的输出结果应该是:hello,但是因为空字符串会被转换成false, b = '' || "