ECMAScript6(4)

六、异步编程解决方案

1.Promise

Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象,从它可以获取异步操作的消息,从而可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

Promise对象提供统一的接口,使得控制异步操作更加容易它还提供了统一的API,各种异步操作都可以用同样的方法进行处理。

Promise构造函数接收一个函数作为参数,这个函数的两个参数分别是resolvereject。它们是两个函数,由JavaScript引擎提供,不用自己部署。如果调用resolve()函数和reject()函数时带有参数,那么它们的参数会被传递给回调函数。

resolve()函数的作用是,将Promise对象的状态从未完成变为成功(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

reject()函数的作用是,将Promise对象的状态从未完成变为失败(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

 

Promise实例生成以后,可以用then方法then方法定义在原型对象Promise.prototype上)分别指定Resolved状态和Rejected状态的回调函数

.then(function(){ //success }, functions(){ //error });

Promise.prototype.then方法的作用是为Promise实例添加状态改变时的回调函数。then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。例如:

function getPromise(){
    let promise = new Promise(function(resolve,reject){
        setTimeout(()=>{
            let random = Math.random();
            if(random>0.5){
                resolve(random);
            } else {
                reject(random);
            }
        },1000)
    });
    return promise;
}
let promise = getPromise();
promise.then((result)=>{        //result为resolve(random)中的random
    console.log("success",result);
},(error)=>{                //result为reject(random)中的random
    console.log("error",error);
})

then方法返回的是一个经回调函数处理后的新的Promise实例,因此可以采用链式写法,即then方法后面再调用另一个then方法。如果使用两个then方法,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。一般来说,不在then方法里面定义Reject状态的回调函数(即then的第二个参数),是使用catch方法来处理发生错误时的情况。

 

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例

let p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all方法接收一个数组作为参数,p1p2p3都是Promise实例,p的状态由p1p2p3决定

只有p1p2p3的状态都变成fulfilled(成功)p的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值会传递给p的回调函数。

let p = Promise.all([p1,p2,p3]);
p.then((result)=>{
    console.log("全部成功",result);
})
.catch((error)=>{
    console.log("出现问题",error);
})

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

let p = Promise.any([p1, p2, p3]);

上面代码中,Promise.any方法接收一个数组作为参数,p1p2p3都是Promise实例,p的状态由p1p2p3决定:

p1p2p3的状态有一个变成fulfilledp的状态就会变成fulfilled,第一个状态为fulfilledPromise实例就会将返回结果传递给p的回调函数。

p1p2p3之中全部rejectedp的状态变成rejected,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

let p = Promise.any([p1,p2,p3]);
p.then((result)=>{
    console.log("有承诺成功",result);
})
.catch((error)=>{
    console.log("全部出现问题",error);
})

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。下面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变那个率先改变的Promise实例的返回值就传递给p的回调函数(race本身就有竞跑的意思)。

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

上面代码中,Promise.race方法接收一个数组作为参数,p1p2p3都是Promise实例,p的状态由p1p2p3决定

当p1p2p3的状态有一个变成fulfilledp的状态就会变成fulfilled,第一个状态为fulfilledPromise实例就会将返回结果传递给p的回调函数。

当p1p2p3之中有一个被rejectedp的状态就变成rejected,第一个被rejectPromise实例就会将返回结果传递给p的回调函数。

p的状态取决于第一个状态改变的那个承诺对象的状态。

let p = Promise.race([p1,p2,p3]);
p.then((result)=>{
    console.log("有承诺成功",result);
})
.catch((error)=>{
    console.log("出现问题",error);
})

Promise.resolve方法将现有对象转为Promise对象,例如:

// 基于promise的异步操作的封装
function get_promise(url){
    // 将异步操作封装到一个承诺对象中
    return new Promise((resolve,reject)=>{
        let xhr = new XMLHttpRequest();
        xhr.open("GET",url);
        xhr.responseType = "json";
        xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xhr.send();
        xhr.onreadystatechange = function(){
            if(this.readyState === 4){
                if(this.status === 200){
                    // 承诺成功
                    resolve(this.response);
                } else {
                    // 承诺失败
                    reject(this);
                }
            }
        }
    })
}
let url = "http://134.175.100.63:6677/customer/findAll";
let $_ajax = $.get(url);
console.log($_ajax)
let p = Promise.resolve($_ajax);
p.then((result)=>{            //result为resolve(this.response)中的this.response
    console.log("result:",result);
})

当Promise.resolve方法不带有任何参数时,直接返回一个Resolved状态的Promise对象;当参数是一个Promise实例,Promise.resolve将不做任何修改、原封不动地返回这个实例;当参数是一个thenable对象具有then方法的对象Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法,例如:

let obj = {
    name:"terry",
    then:function(){
        console.log("-----");
        return "thenable"
    }
}
let p2 = Promise.resolve(obj);
console.log("p2:",p2);

参数不是具有then方法的对象,或根本就不是对象时,只是一个原始值Promise.resolve方法返回一个新的Promise对象,状态为Resolved

Promise.reject(“reason”)方法也会返回一个新的Promise实例,该实例的状态为rejected

let p = Promise.reject(“出错了”);

//等同于

let p = new Promise((resolve, reject) => reject(“出错了”));

 

Promise.prototype.finally方法用于指定无论Promise对象最后状态如何,最后都会执行的回调函数常见应用有服务器处理Promise请求后,用finally结束加载状态:

get_promise(url);
.then((result)=>{ 
    console.log("请求成功1:",result);
    return result;
})
.catch(()=>{
    alert("出现异常");
})
.finally(()=>{
    alert("请求结束");
})

 

2.Generator

Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同可以把Generator函数理解成一个封装了多个内部状态的状态机。

语法上,Generator函数是一个普通函数,但是有两个特征一是,function关键字与函数名之间有一个星号一般紧贴function);二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)后面可接值或函数/函数调用。例如:

function* helloWorldGenerator(){
    yield 'hello';
    yield ‘world’;
    return 'ending';    //一般情况下不加return返回值语句,因为它的done值为true
//该函数有三个状态:hello、world和return语句(结束执行)
}
let hw = helloWorldGenerator();

调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是返回一个指向内部状态的指针对象,也就是遍历器对象(可以把Generator函数理解为等价于迭代器生成函数),它可以调用next方法:

hw.next();    //{ value: 'hello', done: false }
hw.next();    //{ value: 'world', done: false }
hw.next();    //{ value: 'ending', done: true}
hw.next();    //{ value: undefined, done: true }

 

由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实是提供了一种可以暂停执行的函数,yield表达式就是暂停标志。

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为JavaScript 提供了手动的“惰性求值”的语法功能

yield表达式只能出现在Generator函数中,它本身没有返回值,或者说总是返回undefined,而next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。该功能可以Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。例如:

let $ = {
    //基于promise的异步操作的封装
    get(url){
        //将异步操作封装到一个承诺对象中
        return new Promise((resolve,reject)=>{
            let xhr = new XMLHttpRequest();
            xhr.open("GET",url);
            xhr.responseType = "json";
            xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
            xhr.send();
            xhr.onreadystatechange = function(){
                if(this.readyState === 4){
                    if(this.status === 200){
                        //承诺成功
                        resolve(this.response);
                    } else {
                        //承诺失败
                        reject(this);
                    }
                }
            }
    });
    }
}
function* foo(){
let c_url = "http://134.175.100.63:6677/customer/findAll";
let o_url = "http://134.175.100.63:6677/order/findAll";
    let customers = yield call($.get,c_url);
    let order = yield call($.get,o_url);
}
//异步函数的执行器
function call(handler,params){
//将接收到的第二个参数作为第一个参数方法的参数
    handler(params)
    .then((response)=>{    //在上一个请求结束后再去调用下一个请求
//response为resolve(this.response)的this.response
        //将当前请求结果作为yield表达式的返回值返回
//next参数作为上一个yield表达式的返回值,此处由customer变量接收
        iterator.next(response);
    })
}
let iterator = foo();    //Generator函数返回一个迭代器对象
iterator.next();        //手动让Generator函数往下执行语句

可以把异步操作写在yield表达式里,等到调用next方法的时候再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法的时候才执行,体现异步操作的同步表达。例如:

function* loadUI(){
    showLoadingScreen();
    yield loadUIDataAsynchronously();
    hideLoadingScreen();
}
let loader = loadUI();
loader.next();        //加载UI
loader.next();        //卸载UI

 

3.Async

ES2017标准引入了async函数,使得异步操作变得更加方便相当于Generator函数的语法糖(更方便易用的语法特性),使得Generator函数的应用更加简化,如可以将Generator函数中的函数foo例子简化为:

async function foo(){
let c_url = "http://134.175.100.63:6677/customer/findAll";
let o_url = "http://134.175.100.63:6677/order/findAll";
    let customers = await $.get(c_url);
    let order = await $.get(o_url);
    //return xxx;
}
let promise = foo();
promise.then((result)=>{
    //result为函数foo中return语句的值
})

 

Generator函数的执行必须靠执行器,所以才有了co模块(一种辅助的第三方模块),而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncawait比起星号和yield,语义更清楚。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

yield命令后面只能是值、函数或Promise对象,而async函数的await命令后面可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即resolvedPromise对象)

async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象更加方便,可以直接使用then方法指定下一步操作。

async函数完全可以看作多个异步操作包装成的一个Promise对象,而await命令就是内部then命令的语法糖。

 

4.axios

axios基于Promiseajax框架(或者说是基于PromiseHTTP库),它既可以运行在浏览器上又可以运行在nodejs上。

运行在浏览器中时,axios通过封装XMLHttpRequest来实现,使用时可以通过script标签导入(<script src="xxxx/axios.min.js"></script>);如果运行在NodeJS中,它通过封装http模块来实现,可以通过模块化机制将其加载到项目中($ cnpm install axios --save)。

 

axios的底层接口是axios(config)config是它的配置对象,结果返回一个ajax承诺对象(Promise)。axios请求操作的一些config配置如下:

{
   // `url` 是用于请求的服务器 URL(必须)
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // default

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data, headers) {
    // 对 data 进行任意转换处理
    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理
    return data;
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },

   // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,

   // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default

   // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // default
}

 

axios.defaults用于保存默认的axios配置信息,这个配置将对区域内所有axios操作产生影响,例如:

axios.defaults.baseURL

axios.defaults.timeout

axios.defaults.transformRequest

axios.defaults.transformResponse

axios.defaults.headers.common

axios.defaults.headers.post

axios.defaults.headers.get

...

 

在使用axios请求的别名方法时,urlmethoddata这些属性都不必在配置中指定,axios请求方法的别名有:

axios.request(config)

axios.get(url[, config])

axios.delete(url[, config])

axios.head(url[, config])

axios.options(url[, config])

axios.post(url[, data[, config]])

axios.put(url[, data[, config]])

axios.patch(url[, data[, config]])

 

axios请求的响应为then回调函数中的参数,也就是axios的请求成功的结果,这个结果不是服务器直接返回的对象,而是二次封装后的对象。axios请求的响应结构如下:

{
  // `data` 由服务器提供的响应(这个data中包含服务器直接返回的对象)
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,

  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 服务器响应的头
  headers: {},

   // `config` 是为请求提供的配置信息
  config: {},

  // `request`是生成此响应的请求
  request: {}
}

 

axios拦截器机制可以在请求或响应被thencatch处理前拦截它们并添加自定义处理:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
// 可用于为所有类型的响应错误设定一个统一的反馈
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

 

添加与查询顾客信息案例:

 

let axios = require("axios")
let qs = require("qs")

// 配置默认基路径
axios.defaults.baseURL = "http://127.0.0.1:6677";
axios.defaults.headers.common["Content-Type"] = "application/x-www-form-urlencoded";
axios.defaults.transformRequest = [(data)=>{
    return qs.stringify(data);
}]
// 拦截器
axios.interceptors.response.use(function(response){
    return response;
},function(error){
    // 任何一个ajax请求出现异常都会打印的错误信息
    console.log("error!!!");
    return Promise.reject(error);
});

function saveCustomer(){
    let data = {
        realname:"张三004",
        telephone:"18812344321"
    }
    axios({
        url:"/customer/saveOrUpdate",
        method:"post",
        data
    })
    .then((response)=>{
        console.log(response.data);
    })
}
function findAllCustomer(){
    axios({
        url:"/customer/findAll",
        method:"get"
    })
    .then((response)=>{
        console.log("顾客信息",response.data);
    })
}
saveCustomer();
findAllCustomer();
posted @ 2019-10-12 16:08  我的祈愿  阅读(168)  评论(0)    收藏  举报