10.(ECMAScript)es6完全解读(9)

1. 重点提炼

  • 异步基础知识
  • ajax原理
  • callbackhell
  • Promise
  • Generator

2. 异步操作必备知识

  • JS是单线程的
  • 同步任务 与 异步任务
  • Ajax原理
  • Callback Hell

JS是单线程 => 如在超市中买完东西,需要结账,在柜台前面顾客排成一列,正常情况下同一时间,柜台上的服务员,只能为一个顾客结账,等上一位顾客结账完毕后,则下一位顾客再结账。单线程,即同一时间内,只能处理一个任务。

js为啥设计成单线程语言呢?这和js的用途是有关的,实际在做前端开发的时候,主要用到的最基本的三种技术,即为html=> 布局及内容等待、css=> 样式、js => 与用户交互和dom操作等。这决定了它只能是单线程,否则会带来很复杂的同步问题。如果设置成多线程语言,操作dom某个节点时,一个线程对dom节点进行增加操作,同一时间,另一个线程对dom节点进行删除操作,对于网页而言到底是增加还是删除,很难处理。因此为了避免这些复杂的问题和矛盾,js的作者在设计初期,将js语言设计成单线程的。

如果前一个js任务耗时特别长,那么后一个任务不得不一直等着,因此js作者将任务分为两类 =>

同步任务异步任务

  • 同步:只有前一个任务执行完毕,才能执行后一个任务
  • 异步:当同步任务执行到某个 WebAPI时,就会触发异步操作,此时浏览器会单独开线程去处理这些异步任务。
const a = 2
const b = 3
console.log("同步任务",a + b) // 同步任务
// 异步任务
setTimeout(() => {
    console.log(a + b)
}, 1000)
console.log('异步任务')

image-20201125203304960

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.89
Branch: branch02

commit description:a2.89(异步操作必备知识——同步任务 与 异步任务)

tag:a2.89


现在大部分项目都是前后端数据分离 ,前端的页面显示,前端需要发送ajax请求后端的数据。但这个请求,不会立马完成,这有很多因素,如网络因素,并且后端需要进行数据库操作等,都需要耗费实际,因此它也是异步操作。

常考面试题 =>

先执行同步操作。

注意:定时器最小时间也是4毫秒,因此不会是0的,虽然设置的是0,但执行时间必然大于等于4毫秒。

// 1 3 2
console.log(1)
setTimeout(() => {
    console.log(2)
}, 0)
console.log(3)

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.90
Branch: branch02

commit description:a2.90(异步操作必备知识——常考面试题)

tag:a2.90


任务进入执行栈,首先判断任务是同步的?还是异步的?

如果是异步则先进入Event Table中,紧接着在事件队列中,队列是先进先出的。如定时器时间到了,则该方法从事件表中进入事件队列中,在队列中排队等待,等主线程任务执行完毕,即主线程空闲,轮到事件队列的任务,再执行。

image-20201125213145900


伪代码 =>

实际task是在2s后执行吗?

并不会,因为首先进入事件表,再过2s后进入事件队列,假设还有一个很复杂的同步任务,需要执行5s,执行到2s的时候,task任务才进入到事件队列了,它得等3s,主线程执行完这个复杂的同步任务,task才能进入主线程进行执行。

因此定时器的时间未必是准确的。

// 伪代码
setTimeout(()=>{
    task() // 表示一个任务
}, 2000)
sleep(5000) // 表示一个很复杂的同步任务,要执行很长时间

3. Ajax原理与Callback Hell

Ajax => 是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

不刷新页面的情况下,对页面的局部进行部分刷新,即局部刷新。

Ajax原理 => 原生js代码实现

1、创建XMLHttpRequest对象

2、发送请求

3、服务端响应

// Ajax的原理
function ajax(url, callback) {
    // 1、创建XMLHttpRequest对象
    var xmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest() // IE7之后的版本
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2、发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3、服务端响应
    xmlhttp.onreadystatechange = function () {
        // 正确返回
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            callback(obj)
        }
    }
}

// var url = 'http://abcxsdsa.com'
// ajax(url, res => {
//     console.log(res)
// })

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.91
Branch: branch02

commit description:a2.91(Ajax原理与Callback Hell——Ajax原理 => 原生js代码实现)

tag:a2.91


callback hell =>

如发送三个ajax请求,它们是有顺序依赖关系,但是ajax请求是异步操作。

因此需要在第一次请求的回调函数中进行第二次请求,然后依次下去。但是如果是更多请求依赖,就导致了回调地狱,代码结构越来越深,代码可读性极差,如果逻辑复杂,导致代码可维护性很差。 => es6提出了Promise,做了相关优化。

利用JSON文件,模拟ajax请求。

// 1 -> 2 -> 3
// callback hell
ajax('static/a.json', res => {
    console.log(res)
    ajax('static/b.json', res => {
        console.log(res)
        ajax('static/c.json', res => {
            console.log(res)
        })
    })
})

如果嵌套变多,代码层次就会变深,维护难度也随之增加。

这就被称为 “回调地狱” 或者“回调深渊”

image-20201125232918088

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.92
Branch: branch02

commit description:a2.92(Ajax原理与Callback Hell——callback hell )

tag:a2.92


4. 异步编程解决方案Promise

Promise就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise可以支持多个并发的请求,获取并发请求中的数据这个promise可以解决异步的问题,本身不能说promise是异步的。

Promise接收一个回调函数

这里一共对应两个参数 => resolvereject。它们是两个函数,由 JavaScript引擎提供,不用自己部署。

resolve=> 异步操作执行成功的一个回调函数

reject=> 异步操作执行失败的一个回调函数

两者都可对then传参。

  • 处理结果正常的话,调用resolve(处理结果值),将Promise对象的状态从“未完成”变为“成功”(即从 pending变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
  • 处理结果错误的话,调用reject(Error对象),将Promise对象的状态从“未完成”变为“失败”(即从 pending变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

Promise的精髓 => 异步操作的状态管理

在项目中会加入异步逻辑,如何进行成功和失败的返回。

const promise = new Promise(function(resolve, reject) {
    // ... some code

    if ( /* 异步操作成功 */ ) {
        resolve(value)
    } else {
        reject(error)
    }
})

promise.then(function(value) {
    // success
}, function(error) {
    // failure
})

返回成功 =>

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('123')
        resolve('成功')
    }, 1000)
}).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

image-20201126100506001

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.93
Branch: branch02

commit description:a2.93(异步编程解决方案Promise——返回成功)

tag:a2.93


返回失败 =>

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('123')
        reject('失败')
    }, 1000)
}).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

image-20201126100645370

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.94
Branch: branch02

commit description:a2.94(异步编程解决方案Promise——返回失败)

tag:a2.94


注意:Promise中的回调函数里是同步代码会立即执行,所以Promise里的代码不一定是等一会再执行。

let p = new Promise((resolve, reject) => {
    console.log(1)
    resolve()
})
console.log(2)
p.then(res => {
    console.log(3)
})

同步代码立即执行,所以先输出12then相当于跟着new Promise的微任务,即理解为先执行正常的代码后,再执行then相关代码。

1

2

3

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.95
Branch: branch02

commit description:a2.95(异步编程解决方案Promise——笔试题1)

tag:a2.95


Promise对应三种状态,状态不可逆

new的时候 => pending(进行中)

resolve=>fulfilled / resolved (成功)

reject=> rejected(失败)

image-20201126101352468

注意

状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending),而且只能是从 pendingfulfilled或者 rejected


4.1 常用方法

4.1.1 Promise.prototype.then()

promise.then(onFulfilled, onRejected)

var promise = new Promise(function(resolve, reject) {
    resolve('传递给then的值')
})
promise.then(function(value) {
    console.log(value)
}, function(error) {
    console.error(error)
})

以上代码创建一个 Promise对象,定义了处理 onFulfilledonRejected的函数(handler),然后返回这个 Promise对象。

这个 Promise对象会在变为 resolve或者 reject的时候分别调用相应注册的回调函数。

  • handler返回一个正常值的时候,这个值会传递给 Promise对象的 onFulfilled方法。
  • 定义的 handler中产生异常的时候,这个值则会传递给 Promise对象的 onRejected方法。

4.1.2 Promise.prototype.catch()

捕获异常是程序质量保障最基本的要求,可以使用 Promise对象的 catch方法来捕获异步操作过程中出现的任何异常。

function test() {
    return new Promise((resolve, reject) => {
        reject(new Error('es'))
    })
}

test().catch((e) => {
    console.log(e.message) // es
})

以上代码展示了如何使用 catch捕获 Promise对象中的异常,有人会会问 catch捕获的是 Promise内部的 Error还是 Reject?上面的示例既用了 reject也用了 Error,到底是哪个触发的这个捕获呢?

function test() {
    return new Promise((resolve, reject) => {
        throw new Error('wrong')
    })
}

test().catch((e) => {
    console.log(e.message) // wrong
})

以上代码对比着上个代码就能明显感受出来的,throw Errorreject都触发了 catch的捕获,而第一个用法中虽然也有 Error但是它不是 throw,只是 reject的参数是 Error对象,换句话说 new Error不会触发 catch,而是 reject

注意

不建议在 Promise内部使用 throw来触发异常,而是使用 reject(new Error()) 的方式来做,因为 throw的方式并没有改变 Pronise的状态


4.1.3 code


let p1 = new Promise((resolve, reject) => {
    resolve(1)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3)
    }, 1000)
})
console.log(p1) // resolved
console.log(p2) // pending
console.log(p3) // pending

因为对状态没进行处理,所以浏览器报错,一会补上thencatch即可。

浏览器已经运行到1s后了,因此PromiseState里不是pending(在1s前,PromiseState里是pending),而是对应的resolved/ fulfilled/ rejected了。

image-20210215002011175

image-20201126102128089

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.96
Branch: branch02

commit description:a2.96(异步编程解决方案Promise——状态)

tag:a2.96


rejected可以通过then的第二个参数(回调函数),或者.catch获取 =>

let p1 = new Promise((resolve, reject) => {
    resolve(1)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(3)
    }, 1000)
})
console.log(p1) // resolved
console.log(p2) // pending
console.log(p3) // pending

setTimeout(() => {
    console.log(p2)
}, 2000)
setTimeout(() => {
    console.log(p3)
}, 2000)

p1.then(res => {
    console.log(res)
})
p2.then(res => {
    console.log(res)
})
p3.catch(err => {
    console.log(err)
})

image-20201126111901676

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.97
Branch: branch02

commit description:a2.97(异步编程解决方案Promise——then与catch)

tag:a2.97


Promise状态不可逆

let p = new Promise((resolve, reject) => {
    reject(2)
    resolve(1)
})
p.then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

只要promise状态从pending变为resolved/ fulfilledrejected,就定型了,状态确定下来后就不可改变,即不可能再发生状态变化了。

2

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.98
Branch: branch02

commit description:a2.98(异步编程解决方案Promise——Promise状态不可逆)

tag:a2.98


Promise改造Ajax的回调地狱请求 =>

回调地狱 => 链式操作,注意一定要返回Promise对象,下一次的操作建立在上一次的promise对象上。

successCallback && successCallback(obj) => 判断当前参数是否传递,如果传递了则调用。

function ajax(url, successCallback, failCallback) {
    // 1、创建XMLHttpRequest对象
    var xmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2、发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3、服务端响应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            successCallback && successCallback(obj)
        } else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
            failCallback && failCallback(xmlhttp.statusText)
        }
    }
}

new Promise((resolve, reject) => {
    ajax('static/a.json', res => {
        console.log(res)
        resolve()
    })
}).then(res => {
    console.log('a成功')
    return new Promise((resolve, reject) => {
        ajax('static/b.json', res => {
            console.log(res)
            resolve()
        })
    })
}).then(res => {
    console.log('b成功')
    return new Promise((resolve, reject) => {
        ajax('static/c.json', res => {
            console.log(res)
            resolve()
        })
    })
}).then(res => {
    console.log('c成功')
})

image-20201126114628120

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.99
Branch: branch02

commit description:a2.99(异步编程解决方案Promise——Promise改造Ajax的回调地狱请求)

tag:a2.99


以上写法有些笨拙,可再进行优化 =>

实际promise的逻辑里,只有url不太一样,因此可以封装成方法。

function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        }, err => {
            reject(err)
        })
    })
}
getPromise('static/a.json')
    .then(res => {
        console.log(res)
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res)
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res)
    })

image-20201126120917933

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.01
Branch: branch02

commit description:a3.01(异步编程解决方案Promise——Promise请求封装成方法)

tag:a3.01


加上失败的promise=>

function ajax(url, successCallback, failCallback) {
    // 1、创建XMLHttpRequest对象
    var xmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2、发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3、服务端响应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            successCallback && successCallback(obj)
        } else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
            failCallback && failCallback(xmlhttp.statusText)
        }
    }
}

function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        }, err => {
            reject(err)
        })
    })
}

getPromise('static/aa.json')
    .then(res => {
        console.log(res)
        return getPromise('static/b.json')
    }, err => {
        console.log(err)
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res)
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res)
    })

image-20201126121613849

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.02
Branch: branch02

commit description:a3.02(异步编程解决方案Promise——加上失败的promise )

tag:a3.02


如果a读取失败,不对它进行单独处理,而是统一处理,可再最后加入catch

getPromise('static/aa.json')
    .then(res => {
        console.log(res)
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res)
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })

image-20201126122153798

getPromise('static/a.json')
    .then(res => {
        console.log(res)
        return getPromise('static/bb.json')
    }).then(res => {
        console.log(res)
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })

image-20201126122241339

getPromise('static/a.json')
    .then(res => {
        console.log(res)
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res)
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })

image-20201126122311552

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.03
Branch: branch02

commit description:a3.03(异步编程解决方案Promise——异步出错统一处理,最后加catch)

tag:a3.03


5. Promise的静态方法

  • Promise.resolve()
  • Promise.reject()
  • Promise.all()
  • Promise.race()

5.1 Promise.resolve()

一般情况下我们都会使用 new Promise()来创建 Promise对象,但是除此之外我们也可以使用其他方法。

使用 Promise.resolvePromise.reject 这两个方法。

静态方法 Promise.resolve(value) 可以认为是new Promise() 方法的快捷方式。

比如 Promise.resolve('success') 可以认为是以下代码的语法糖。

在这段代码中的resolve('success') 会让这个 Promise对象立即进入确定(即resolved)状态,并将 ‘success’ 传递给后面 then里所指定的onFulfilled 函数。

方法 Promise.resolve(value) 的返回值也是一个 Promise对象,所以我们可以像下面那样接着对其返回值进行.then 调用。

let p1 = Promise.resolve('success')
console.log(p1)
p1.then(res => {
    console.log(res)
})

image-20201126132044902

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.04
Branch: branch02

commit description:a3.04(Promise的静态方法——Promise.resolve())

tag:a3.04

Promise.resolve 作为 new Promise()的快捷方式,在进行 Promise对象的初始化或者编写测试代码的时候都非常方便。


5.2 Promise.reject()

Promise.reject(error)是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。

比如 Promise.reject('fail') 就是下面代码的语法糖形式。

let p2 = Promise.reject('fail')
console.log(p2)
p2.catch(err => {
    console.log(err)
})

image-20201126132255621

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.05
Branch: branch02

commit description:a3.05(Promise的静态方法——Promise.reject())

tag:a3.05


应用场景 => 有时没有promise实例,也想调用thencatch方法,则需要Promise静态方法包裹。

定义一个方法返回promise,如果失败的时候,则返回一个字符串。则字符串下没有then方法,可以包裹Promise=> 利用Promise.reject

function foo(flag) {
    if (flag) {
        return new Promise(resolve => {
            // 异步操作
            resolve('success')
        })
    } else {
        // return 'fail'
        return Promise.reject('fail')
    }
}

foo(true).then(res => {
    console.log(res)
})

foo(false).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

image-20201126133525044

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.06
Branch: branch02

commit description:a3.06(Promise的静态方法——应用场景 => 有时没有promise实例,也想调用then和catch方法,则需要Promise静态方法包裹。)

tag:a3.06


5.3 Promise.all()

Promise.all(promiseArray)

三个异步操作,希望三个异步操作都完成后,再做一些事情。

如在项目中,想上传一些图片到服务器上面,但是服务器提供的上传接口每次只能上传一张图片。这个时候就需要循环图片,一张一张的上传了。如三张图片,都上传完毕后,提示用户上传成功。那如何知道三张图片已经上传完毕了呢?这跟当时的网络情况、图片大小、服务器响应时间等诸多因素都有关。

Promise.all([…]) => 参数对应一个数组,里每一个内容都对应一个Promise对象。

作用:等数组中每一个promise对象都执行完成后,再执行Promise.allthen方法。

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1)
        resolve('1成功')
    }, 2000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2)
        resolve('2成功')
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3)
        resolve('3成功')
    }, 3000)
})

Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

Promise.all 生成并返回一个新的 Promise对象,所以它可以使用 Promise实例的所有方法。参数传递promise数组中所有的 Promise对象都变为resolve的时候,该方法才会返回, 新创建的 Promise则会使用这些 promise的值。

得到的res是一个上述三个promise调用resolve配置参数组成的数组。

image-20201126140055519

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.07
Branch: branch02

commit description:a3.07(Promise的静态方法——Promise.all)

tag:a3.07


假设某一个异步失败了

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1)
        resolve('1成功')
    }, 2000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2)
        reject('2失败')
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3)
        resolve('3成功')
    }, 3000)
})
Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

Promise.all参数数组中只要有一个对象返回状态是失败,则认为整个都是失败的,则进入catch或者err第二个参数中去。即如果参数中的任何一个promisereject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise对象。

image-20201126140703624

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.08
Branch: branch02

commit description:a3.08(Promise的静态方法——Promise.all对于rejected的情况)

tag:a3.08


5.4 Promise.race()

Promise.race(promiseArray)

Promise.all相对的是Promise.race

当数组其中的promise对象,只要有一个完成,则认为整个都是完成的。而all是所有都完成,才认为是都完成。

Promise.race生成并返回一个新的 Promise对象。

参数 promise数组中的任何一个 Promise对象如果变为 resolve或者 reject的话, 该函数就会返回,并使用这个 Promise对象的值进行 resolve或者 reject

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1)
        resolve('1成功')
    }, 2000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2)
        resolve('2成功')
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3)
        resolve('3成功')
    }, 3000)
})

Promise.race([p1, p2, p3]).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

image-20201126165205439

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.09
Branch: branch02

commit description:a3.09(Promise的静态方法——Promise.race)

tag:a3.09


如果有一个失败了。

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1)
        resolve('1成功')
    }, 2000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2)
        reject('2失败')
    }, 1000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3)
        resolve('3成功')
    }, 3000)
})

Promise.race([p1, p2, p3]).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

数组中对应的某个promise对象失败了,则认为是都失败了。

image-20201126165543787

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.10
Branch: branch02

commit description:a3.10(Promise的静态方法——Promise.race对于rejected的情况)

tag:a3.10


5.5 应用场景

promise.all应用场景 =>

如有一个图片数组,里面存储了很多图片数据。

图片需要一张张的上传,当所有图片都上传完了以后,给用户一个提示,告诉他上传完成。

定义一个promise数组是一个空值。

遍历图片数组,一个一个图片的上传,上传图片的过程是一个异步操作。每次上传一个图片都new一个promise对象,并将其添加到promise数组中,在其中完成图片上传的操作。当图片上传以后调用resolve。因为有三张图片,因此会new出3个promise对象。

通过Promise.all,把promise数组传入即可。

同时用户上传图片后后端(云存储)返回的url通过resolve(url) => 传递给all,然后通过res数组获取,将每个图片的url插入到数据库中。

const imgArr = ['1.jpg', '2.jpg', '3.jpg']
let promiseArr = []
imgArr.forEach(item => {
    promiseArr.push(new Promise((resolve, reject) => {
        // 图片上传的操作(省略)
        resolve()
    }))
})
Promise.all(promiseArr).then(res => {
    // 插入数据库的操作
    console.log('图片全部上传完成')
})

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.11
Branch: branch02

commit description:a3.11(Promise的静态方法——promise.all应用场景)

tag:a3.11


promise.race应用场景 =>

如果当前加载图片,图片有可能加载失败,需要给图片加载设置一个超时时间(2s)。

使用race,因为要么图片加载成功,要么定时器到达时间。

function getImg() {
    // 图片加载是异步过程 放入promise
    return new Promise((resolve, reject) => {
        let img = new Image()
        // 图片加载成功
        img.onload = function () {
            resolve(img)
        }
        img.src = 'http://www.xxx.com/xx.jpg'  // 失败的地址
    })
}

function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // 图片加载到时
            reject('图片请求超时')
        }, 2000)
    })
}

Promise.race([getImg(), timeout()]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

image-20201126200207462

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.12
Branch: branch02

commit description:a3.12(Promise的静态方法——promise.race应用场景-图片请求超时)

tag:a3.12


function getImg() {
    // 图片加载是异步过程 放入promise
    return new Promise((resolve, reject) => {
        let img = new Image()
        // 图片加载成功
        img.onload = function () {
            resolve(img)
        }
        // img.src = 'http://www.xxx.com/xx.jpg'  // 失败的地址
        img.src = 'https://avatar.csdnimg.cn/8/1/4/3_u013946061.jpg'  // 成功的地址
    })
}

image-20201126200328392

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.13
Branch: branch02

commit description:a3.13(Promise的静态方法——promise.race应用场景-图片请求成功)

tag:a3.13


promise不仅可以解决回调地狱的问题,还可以处理多个并发的请求,获取并发请求各种各样的数据,promise对异步状态进行管理,它可以解决异步的问题,但promise本身不是异步的,而是promise里面可以写异步的操作,promise可以很好的对这些异步状态进行管理。


6. 异步编程解决方案Generator

什么是 JavaScript Generators呢?通俗的讲Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。

普通的函数在执行过程中,不能再干别的事情。而Generator函数,可以在函数执行的时候进行暂停,又可在暂停的地方继续执行。

Generator函数 => 在function后加*,在其内存在关键字yield

Generator函数,无法直接调用,需要手动执行它,需要用next执行,有点类似调试程序时的单步执行。Generator函数执行是可以暂停的,然后可以继续上一次暂停的状态。

function* foo() {
    for (let i = 0; i < 3; i++) {
        yield i
    }
}
console.log(foo())
let f = foo()
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())

该函数一共只能循环3次,执行第四次实际已经执行完成了,所以valueundefined,即yield后的表达式的值,donetrue,代表函数已经执行完毕了。

常规的循环只能一次遍历完所有值,Generator可以通过调用 next方法拿到依次遍历的值,让遍历的执行变得“可控”

image-20201126213318116

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.14
Branch: branch02

commit description:a3.14(异步编程解决方案Generator——Generator函数)

tag:a3.14


这个是 Generator的定义方法,有几个点值得注意:

  1. **比普通函数多一个 ***
  2. 函数内部用 yield(打断点)来控制程序的执行的“暂停”
  3. 函数的返回值通过调用 next来“恢复”程序执行

注意

Generator函数的定义不能使用箭头函数,否则会触发 SyntaxError错误

这些做法都是错误的❌。

let generator = * () => {} // SyntaxError
let generator = () * => {} // SyntaxError
let generator = ( * ) => {} // SyntaxError

Generator函数实际不会立即执行,而是返回一个生成器迭代器的对象

yield 关键字用来暂停和恢复一个生成器函数

当迭代器函数调用next时,其实在其内部就会执行yield后的语句为止,调用一次,执行一次yield后的语句。再调用next的时候,yield后的语句继续执行。

next函数返回一个对象,value代表yield后的表达式返回值,done代表后续是否有yield的语句。

注意:Generator函数不能作为构造函数去使用,只能返回一个生成器迭代器的对象。yield关键字只能在Generator函数内使用。

function* gen(args) {
    args.forEach(item => {
        yield item + 1
    })
}

报错了,yield 不能写在其他函数内,所以它在forEach函数内不能使用。

image-20201126221240659

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.15
Branch: branch02

commit description:a3.15(异步编程解决方案Generator——yield 不能写在其他函数内)

tag:a3.15


小结:

  1. yield表达式的返回值是 undefined,但是遍历器对象的 next方法可以修改这个默认值。
 function* gen() {
      let val
      val = yield 1
      console.log( `1:${val}` ) // 1:undefined
      val = yield 2
      console.log( `2:${val}` ) // 2:undefined
      val = yield 3
      console.log( `3:${val}` ) // 3:undefined
  }

  var g = gen()

  console.log(g.next()) // {value: 1, done: false}
  console.log(g.next()) // {value: 2, done: false}
  console.log(g.next()) // {value: 3, done: false}
  console.log(g.next()) // {value: undefined, done: true}

从这个代码可以看出来,yield表达式的返回值是 undefined

  1. yeild * 是委托给另一个遍历器对象或者可遍历对象
  function* gen() {
      let val
      val = yield 1
      console.log( `1:${val}` ) // 1:undefined
      val = yield 2
      console.log( `2:${val}` ) // 2:undefined
      val = yield [3, 4, 5]
      console.log( `3:${val}` ) // 3:undefined
  }
  1. Generator 对象的 next方法,遇到 yield就暂停,并返回一个对象,这个对象包括两个属性:valuedone

参考步骤1的代码可以明确看出来,执行第一句 g.nextgen代码执行到 yield 1,程序暂停,此时返回了一个对象:{value: 1, done: false}



经典面试题

function* gen(x) {
    let y = 2 * (yield(x + 1))
    let z = yield(y / 3)
    return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next()) // NaN
console.log(g.next()) // NaN

image-20201126225202519

实际next函数是可以传递参数的,参数是上一回Generatoryield语句(其后表达式)的返回值。(注意第一次调用next,传递参数没有用处,因为此前没有yield,主要根据调用函数时传递的参数)这里第二次调用next时没有传参,因此2 * undefinedy值必然是NaN,接着NaN / 3,返回的表达式的值则为NaN。第三次执行x + y + z,同理还是NaN

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.16
Branch: branch02

commit description:a3.16(异步编程解决方案Generator——next没有参数的情况)

tag:a3.16


function* gen(x) {
    let y = 2 * (yield(x + 1))
    let z = yield(y / 3)
    return x + y + z
}

let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // y=24  8
console.log(g.next(13)) // z=13 x=5 y=24 42

第一次执行:5 + 1 = 6

第二次执行:2 * 12 / 3 = 812是上一回yeild的值,即(yield(x + 1))

第三次执行:5 + 24+ 13 = 4213是上一回yeild的值,yield(y / 3)

image-20201126230506136

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.17
Branch: branch02

commit description:a3.17(异步编程解决方案Generator——next传参的情况)

tag:a3.17


我们经常玩一些小游戏,比如数数字,敲7,到77的倍数,无限循环转圈去数数

1开始数数,数到7的倍数则输出 =>

通过 Generator我们就能轻松实现,只要调用 n.next我们就知道下一个数是什么了,而使用普通函数却没法做到。

function* count(x = 1) {
    while (true) {
        if (x % 7 === 0) {
            yield x
        }
        x++
    }
}
let n = count()
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)

如果没有Generator,则是死循环,是无法一直实现下去的。

image-20201126231447631

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.18
Branch: branch02

commit description:a3.18(异步编程解决方案Generator——数7应用)

tag:a3.18


Generator如何对异步状态进行管理呢?

function ajax(url, callback) {
    // 1、创建XMLHttpRequest对象
    var xmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
    } else { // 兼容早期浏览器
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2、发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3、服务端响应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            // console.log(obj)
            callback(obj)
        }
    }
}

function request(url) {
    ajax(url, res => {
        getData.next(res)
    })
}

function* gen() {
    let res1 = yield request('static/a.json')
    console.log(res1)
    let res2 = yield request('static/b.json')
    console.log(res2)
    let res3 = yield request('static/c.json')
    console.log(res3)
}
let getData = gen()
getData.next()

getData.next()调用,执行第一个yield,在request方法中,发送ajax请求,再执行回调 => getData.next,并将ajax返回的res赋值给res1。然后依次往下执行。

image-20201126231824872

参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.19
Branch: branch02

commit description:a3.19(异步编程解决方案Generator——实现ajax请求)

tag:a3.19


6.1 常用方法小结

6.1.1 next([value])

Generator对象通过 next方法来获取每一次遍历的结果,这个方法返回一个对象,这个对象包含两个属性:valuedonevalue是指当前程序的运行结果,done表示遍历是否结束。

next是可以接受参数的,这个参数可以让你在 Generator外部给内部传递数据,而这个参数就是作为上一个 yield的返回值。

过程分析 =>

  1. g.next(20)这句代码会执行 gen内部的代码,遇到第一个 yield暂停。所以 console.log('before ${val}') 执行输出了 before 100 ,此时的 val100,所以执行到 yield val返回了 100,注意 yield val并没有赋值给 val

  2. g.next(30) 这句代码会继续执行 gen 内部的代码,也就是 val = yield val这句,因为 next 传入了 30,所以 yield val 这个返回值就是 30,因此 val 被赋值 30,执行到 console.log('return ${val}')输出了 30,此时没有遇到yield 代码继续执行,也就是 while 的判断,继续执行 console.log('before ${val}')输出了 before 30,再执行遇到了 yield val程序暂停。

  3. g.next(40)重复步骤 2

  function* gen() {
      var val = 100
      while (true) {
          console.log( `before ${val}` )
          val = yield val
          console.log( `return ${val}` )
      }
  }

  var g = gen()
  console.log(g.next(20).value)
  // before 100
  // 100
  console.log(g.next(30).value)
  // return 30
  // before 30
  // 30
  console.log(g.next(40).value)
  // return 40
  // before 40
  // 40

6.1.2 return()

return 方法可以让 Generator遍历终止,有点类似 for循环的 break

function* gen() {
    yield 1
    yield 2
    yield 3
}

var g = gen()

console.log(g.next()) // {value: 1, done: false}
console.log(g.return()) // {value: undefined, done: true}
console.log(g.next()) // {value: undefined, done: true}

done可以看出代码执行已经结束。

当然 return也可以传入参数,作为返回的 value值。

function* gen() {
    yield 1
    yield 2
    yield 3
}

var g = gen()

console.log(g.next()) // {value: 1, done: false}
console.log(g.return(100)) // {value: 100, done: true}
console.log(g.next()) // {value: undefined, done: true}

6.1.3 throw()

可以通过 throw方法在 Generator外部控制内部执行的**“终断”**。

function* gen() {
    while (true) {
        try {
            yield 42
        } catch (e) {
            console.log(e.message)
        }
    }
}

let g = gen()
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
// 中断操作
g.throw(new Error('break'))

console.log(g.next()) // {value: undefined, done: true}

7. 异步操作解决方案

  • 回调
  • promise
  • Generator
  • (es7)async和await



(后续待补充)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值