深刻理解javascript系列(十九):从Promise开始到async/await

什么是同步与异步的定义,在这里我就不作记录,直接用代码来表示它们之间的区别。api

首先使用Promise模拟一个发起请求的函数,该函数执行后,会在1s以后返回数值30。数组

function fn() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(30);
        }, 1000);
    })
}复制代码

在该函数的基础上,咱们也可使用async/await语法来模拟同步效果。浏览器

var foo = async function() {
    var t = await fn();
    console.log(t);
    console.log('next');
}

foo();复制代码

输出结果为:bash

Promise {<pending>} //1s 以后依次输出
test:11 30
test:12 next
复制代码

而异步效果则会有不一样的输出结果:异步

var foo = function() {
    fn().then(function(res) {
        console.log(res);
    });
    console.log('next');
}复制代码

输出结果:async

next
// 1s后
30复制代码

好了,接下来咱们正式开始记录Promise函数

Promise

1.  Ajax

Ajax是网页与服务端进行数据交互的一种技术。咱们能够经过服务端提供的接口,用Ajax向服务端请求咱们须要的数据。过程以下:工具

//简单的Ajax原生实现

//服务端接口
var url = 'api/xxxx';
var result;

var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();

XHR.onreadystatechange = function() {
    if(XHR.readyState == 4 && XHR.status == 200) {
        result = XHR.response;
    }
}

复制代码

这样看上去并无什么问题。可是若是这个时候,还须要作另外一个Ajax请求,那么这个新的Ajax请求中的一个参数,则必须从上一个Ajax请求中获取,这个时候咱们就不得不就得在result获得后在进行一次请求。ui

当第三个Ajax(甚至更多)仍然依赖上一个请求的时候,此时的代码就变成了一场灾难。咱们须要不停地嵌套回调函数,以确保下一个接口所须要的参数的正确性,这样的灾难,咱们称为回调地狱。url

因此随着发展,就出现了Promise,他能解决这个问题。

咱们想要确保某代码在某某以后执行时,能够利用函数调用栈,将想要执行的代码放入回调函数中(这是利用同步阻塞)。

function a(callback) {
    console.log('先结婚')
    callback();
}

function b() {
    console.log('再生孩子')
}
a(b);复制代码

插个题外话:“浏览器最先内置的setTimeout与setInterval就是基于回调的思想实现的”。

可是这里也有一个问题,咱们想要在a中执行的代码必须如今callback以前才能输出咱们想输出的。那该怎么办?

其实问题很好解决,除了利用函数调用栈的执行顺序外,还能够利用队列机制来确保咱们想要的代码压后执行。

function a(callback) {
    //将想要执行的代码放入队列中后,根据事件循环机制,
    //就不用把它放到最后面了。
    callback && setTimeout(callback, 0);
    console.log('先结婚')

}

function b() {
    console.log('再生孩子')
}
a(b);复制代码

与setTimeout相似,Promise也能够认为是一种任务分发器,它将任务分配到Promise队列中,一般的流程是首先发起一个请求,而后等待(等待时间无法肯定)并处理请求结果。

var tag = true;
var p = new Promise(function(resolve, reject) {
    if(tag) {
        resolve('tag is true')
    } else {
        reject('tag is false')
    }
})

p.then(function(result) {
    console.log(result);
})
.catch(function(err) {
    console.log(err);
})复制代码

下面简单介绍一下Promise的相关基础知识:

  • new Promise表示建立一个Promise实例对象。
  • Promise函数中的第一参数为一个回调函数,也能够称之为executor。一般状况下,在这个函数中,会执行发起请求操做,并修改结果的状态值。
  • 请求结果有三种状态,分别是pending(等待中,表示尚未获得结果)、resolved(获得了咱们想要的结果,能够继续执行),以及rejected(获得了错误的,或者不是咱们指望的结果,拒绝继续执行)。请求结果的默认状态为pending。在executor函数中,能够分别使用resolve与rejected将状态修改成对应的resolved与rejected。resolve、reject是executor函数的两个参数,它们可以将请求结果的具体数据传递出去。
  • Promise实例拥有的then方法,能够用来处理当请求结果的状态变成resolved时的逻辑。then的第一个参数为一个回调函数,该函数的参数是resolve传递出来的数据。在上面的例子中,result = tag is true。
  • Promise实例拥有的catch方法,可用来处理当前请求结果的状态变成rejectd时的逻辑。catch的第一个参数为一个回调函数,该函数的参数是一个reject传递出来的数据。在上面的例子中,err = tag is false。
下面经过例子来感觉一下Promise的用法。

//demo01.js
function fn(num) {
    //建立一个Promise实例
    return new Promise(function(resolve, reject) {
        if(typeof num == 'number') {
           //修改结果状态值为resolved
           resolve();
        } else {
            // 修改结果状态值为rejected
            reject();
        }
    }).then(function() {
        console.log('参数是一个number值');
    }).catch(function() {
        console.log('参数不是一个number值');
    })
}

//修改参数的类型,观察输出的结果
fn('12');

//注意观察该语句的执行顺序
console.log('next code');复制代码

then方法能够接收两个参数,第一个参数用来处理resolved状态的逻辑,第二个参数用来处理rejected状态的逻辑。

then方法由于返回的还是一个Promise实例对象,所以then方法能够嵌套使用。在这个过程当中,经过在内部函数末尾return的方式,可以将数据持续日后传递。

下面咱们来对Ajax进行一个简单的封装。

var url = 'api/xxxx';

//封装一个get请求的方法
function getJSON(url) {
    return new Promise(function(resolve, reject) {
        //利用Ajax发送一个请求
        var XHR = new XMLHttpRequest();
        XHR.open('GET', url, true);
        XHR.send();

        //等待结果
        XHR.onreadystatechange = function() {
            if(XHR.readyState == 4) {
                if(XHR.status == 200) {
                    try {
                        var res = JSON.parse(XHR.responseText);
                        // 获得正确的结果修改状态并将数据传递出去
                        resolve(response);
                    } catch(e) {
                        reject(e)
                    }
                } else {
                    // 获得错误的结果并抛出异常
                    reject(new Error(XHR.statusText));
                }
            }
        }
    })
}


//封装好之后,使用就很简单了
getJSON(url).then(function(res){
    console.log(res)
})复制代码

2.  Promise.all

当有一个Ajax请求,它的参数须要另外两个甚至更多个请求都有返回结果以后才能肯定时,就须要用到Promise.all来帮助咱们应对这个场景。

Promise.all接收一个Promise对象组成的数组做为参数,当这个数组中全部的Promise对象状态都变成resolved或者rejected时,它才会去调用then方法。

var url1 = 'xxx';
var url2 = 'xxxxx';

function renderAll() {
    return Promise.all([getJSON(url1), getJSON(url2)]);
}

renderAll().then(function(value) {
    console.log(value);
})复制代码

3.  Promise.race

与Promise.all类似的是,Promise.race也是一个Promise对象组成的数组做为参数,不一样的是,只要当数组中的其中一个Promise状态变成了resolved或者rejected时,就能够调用then方法。

async/await

异步问题不只能够用Promise,还能够用async/await,都说这是终极解决方案。

async/await是ES7中新增的语法,虽然如今有些浏览器已经支持了该语法,但在实际使用中,仍然须要在构建工具中配置对该语法的支持才能放心使用。

在函数声明的前面,加上关键字async,这就是async的具体使用。

async function fn() {
    return 30;
}

//或者
const fn = async ()=> {
    return 30;
}

console.log(fn());

//打印结果
Promise {<resolved>: 30}__proto__:Promise[[PromiseStatus]]:"resolved"[[PromiseValue]]:30
复制代码

能够发现打印结果是一个Promise对象,所以能够猜到async实际上是Promise的一个语法糖,目的是为了让写法更加简单,所以也可使用Promise的相关语法来处理后续的逻辑。

fn().then(res=>{
    console.log(res);
})复制代码

await的含义是等待,意思就代码须要等待await后面的函数运行完而且有了返回结果以后,才继续执行下面的代码。这正是同步的效果。

可是须要注意的是,await关键字只能在async函数中使用,而且await后面的函数运行后必须返回一个Promise对象才能实现同步的效果。

当使用一个变量去接收await的返回值时,该返回值为Promise中resolve传递出来的值,也就是PromiseValue。

为了切实感觉下async/await的用法。咱们结合实际开发中最常遇到的异步请求接口的场景。

//先定义接口请求的方法,因为jQuery封装的几个请求方法都是返回Promise实例。
//所以能够直接使用async/await函数实现同步

const getUserInfo = () => $.get('api/asdsd');

const clickHandler = async ()=>{
    try{
        const res = await getUserInfo();
        console.log(res);
        
        // do something
    } catch(e){
        //处理错误逻辑
    }
}复制代码

为了保证逻辑的完整性,在实践中try/catch必不可少。

相关文章
相关标签/搜索