假设我们有三个请求,req1,req2, req3,三个请求后者依赖前者的请求结果。我们先使用Promise封装一个异步请求的方法。

Promise 异步请求

使用Promise可以非常容易的封装一个异步处理的业务,通过reslove/reject两个callback来返回执行结果。

我们使用 Promise 封装一个 http get方法。

// 返回一个 Promise 对象(PromiseStatus: pending)function asyncHttpGet(url) {    return new Promise((resolve, reject) => {        const request = new Request(url, {method: 'GET'})        // 使用 fetch 请求        fetch(request)            .then(response => {                if (200 == response.status) {                    return response.text()                } else {                    // goto catch                    throw new Error("request failed: " + response.status)                }            }).then(html => {                // console.log(html)                resolve(url + " get success")            }).catch(err => {                reject(err)            });    })}

fetch返回的其实就是一个Promise对象,上例是想演示下resolve/reject的使用上下文,如果你早已get,下面给出直接使用fetch的方式:

async function asyncHttpGetV2(url) {    const request = new Request(url, {method: 'GET'})    try {        let res = await fetch(request)            .then(response => response.blob())            .then(blob => {                console.log(blob)                // Promise resolve                return blob            }).catch(err => {                // Promise resolve                throw err            });        // Promise resolve        return res;    } catch (err) {        // Promise reject        throw err    }}

可以发现,fetchreturn 代替了resolvethrow代替了reject,而asyncfetch一样,也是返回了一个 Promise对象,所以async中的return/throw是否也会与自己的返回的Promise对象有关系呢?

回调地狱

Promise 可以优雅的实现异步,但 Promise.then().catch() 的链式结构也带来了回调地狱的问题。如下,我们回调了3层,才能开始写业务逻辑。

var url = window.location.href// 虽然异步了 但 callback hellasyncHttpGet(url).then(res => {    var res1 = res    asyncHttpGet(url).then(res => {        var res2 = res        asyncHttpGet(url).then(res => {            var res3 = res            console.log(res1, res2, res3);            // todo 业务        }).catch(err => {            console.log(err)        })            }).catch(err => {        console.log(err)    })}).catch(err => {    console.log(err)})

async/await

借助 aysnc/await 解决回调地狱的问题,实现同步风格的异步逻辑,这里希望大家能理解透2 & 3两条总结:

  1. aysnc 返回的也是一个 Promise 对象。
  2. 如果返回了return 标量throw Error 则返回 {PromiseStatus: resolved/rejected}Promise对象。
  3. 如果遇到了await装饰的Promise,则返回 {PromiseStatus: pending}Promise。并等待此Promise的执行结果:如果Promise触发了resolve则获取结果并继续向下执行;如果Promise触发了reject则抛出一个异常。所以我们在使用时应将代码使用try...catch封装。
  4. await 关键字只能在 async内使用,await接受一个Promise对象,并将其以 {PromiseStatus: pending}的状态返回。
var url = window.location.hrefasync function getUrls(url1, url2, url3) {    try {        // req1 success or throw error (promise reject)        let res1 = await asyncHttpGet(url1);                // req2 success or throw error (promise reject)        let res2 = await asyncHttpGet(url2);                // req3 success or throw error (promise reject)        let res3 = await asyncHttpGet(url3);                // 三个异步请求都成功 获取最终结果        return [res1, res2, res3].join("\n")    } catch(err) {        // 出现错误,做一些处理        console.log(err)        throw err    }}// 如此 3 个 Promise 请求在 async/await 的封装下变成了一个同步书写风格的异步请求getUrls(url, url, url).then(res => {    console.log(res)    // todo 业务}).catch(err => {    console.log(err)})console.log("request has been sended, and waiting for res")

async 返回的是 Promise对象,所以我们还可以继续使用 async\await封装异步到同步风格。

async function getUrlsMore(url1, url2) {    try {        let getUrls1 = await getUrls(url1, url1, url1)        let getUrls2 = await getUrls(url2, url2, url2)                // Promise resolve        return [getUrls1, getUrls2].join("\n")    } catch (err) {        // Promise reject        throw err    }}getUrlsMore(url, url).then(res => {    console.log(res)}).catch(err => {    console.log(err)})

async/await 和 Promise 的关系

async/awaitPromise 的关系非常的巧妙,await必须在async内使用,并装饰一个Promise对象,async返回的也是一个Promise对象。

async/await中的return/throw会代理自己返回的Promiseresolve/reject,而一个Promiseresolve/reject会使得await得到返回值或抛出异常。

如果方法内无await节点

return 一个字面量则会得到一个{PromiseStatus: resolved}Promise
throw 一个Error则会得到一个{PromiseStatus: rejected}Promise

如果方法内有await节点
async会返回一个{PromiseStatus: pending}Promise(发生切换,异步等待Promise的执行结果)。
Promiseresolve会使得await的代码节点获得相应的返回结果,并继续向下执行。
Promisereject 会使得await的代码节点自动抛出相应的异常,终止向下继续执行。

示例:

方法内无await节点

// 没有 await 修饰的 Promiseasync function foo() {    if (Math.ceil(Math.random() * 10) > 5) {        // {PromiseStatus: resolved}        return "hello world"    } else {        // {PromiseStatus: rejected}        throw new Error("something wrong!")    }}var fooPromise = foo()console.log(fooPromise)fooPromise.then(res => {    console.log(res)}).catch(err => {    console.log(err)})

resolved

rejected

方法内有await节点

注意Promise内的resolve/rejectawait节点的作用。

async function bar() {    try {        // await 返回 {PromiseStatus: pending}        let res = await new Promise((resolve, reject) => {            setTimeout(() => {                if (Math.ceil(Math.random() * 10) > 5) {                    // await 获得结果并继续执行                    resolve("success")                } else {                    // await 中断执行并抛出异常                    reject("failed")                }            }, 2000)        })        // resolve {PromiseStatus: resolved}        return res    } catch (err) {        // reject {PromiseStatus: rejected}        throw err    }}var barPromise = bar()// 查看 barPromise 的 PromiseStatusconsole.log(barPromise)barPromise.then(res => {    console.log(res)}).catch(err => {    console.log(err)})

await配合fetch的实例

then/catch返回的也是Promise对象,在then/catch内使用return/throw来决定返回的Promiseresolved/rejected

// 没有 await 修饰的 Promiseasync function bar() {    try {        // await 返回 {PromiseStatus: pending}        let res1 = await fetch(window.location.href).then(res => {            if (200 == res.status) {                // Promise resolve                return "request success"            } else {                // goto catch                throw "request failed" + res.status            }        }).catch(err => {            // Promise reject            throw err        })                let res2 = await fetch(window.location.href).then(res => {            if (200 == res.status) {                // Promise resolve                return "request success"            } else {                // goto catch                throw "request failed" + res.status            }        }).catch(err => {            // Promise reject            throw err        })                let res3 = await fetch(window.location.href).then(res => {            if (200 == res.status) {                // Promise resolve                return "request success"            } else {                // goto catch                throw "request failed" + res.status            }        }).catch(err => {            // Promise reject            throw err        })                // 三个请求都成功 则返回相应的数据 Promise resolved        return [res1, res2, res3].join("\n")    } catch (err) {        // Promise rejected        throw err    }}var barPromise = bar()// 查看 barPromise 的 PromiseStatusconsole.log(barPromise)// Promise reject 抛出异常 需要使用 catch 捕捉barPromise.then(res => {    console.log(res)}).catch(err => {    console.log(err)})