前端的异步解决方案

Promise对象

Promise 的含义:
    Promise 是异步编程的一种解决方案,简单说就是一个容器,里面保存着某个未来才回结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise 对象的状态不受外界影响

三种状态:

  • pending:进行中
  • fulfilled :已经成功
  • rejected 已经失败

状态改变:
Promise对象的状态改变,只有两种可能:

  • 从pending变为fulfilled
  • 从pending变为rejected

这两种情况只要发生,状态就凝固了,不会再变了,这时就称为resolved(已定型)
jquery中的ajax

$.ajax({
    cache: true,
    type: "GET",
    url: "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1",
    dataType: "json",
    async: true,
    success: function(data) {
        callback(data);
    },
    error: function(data) {
        console.info("error: " + data.responseText);
    }
});

promise封装ajax

const getJson= function(url) {
    return new Promise((resolve, reject) => {
        // 创建 XMLHttpRequest对象,用于在后台与服务器交换数据。
        let request = new XMLHttpRequest()
            //设置向服务器提交的方式
        request.open("GET", url, true)
        request.responseType = 'json'
        request.setRequestHeader("Accept", "application/json");
        // onreadystatechange捕获事件请求的状态
        request.onreadystatechange = function handlerRequest() {
            //readyState为4的时候,代表请求操作已经完成,这意味着数据传输已经彻底完成或失败。
            if (this.readyState === 4) {
                //请求成功
                if (this.status === 200) {
                    resolve(this.response)
                } else {
                    reject(new Error(this.statusText));
                }
            }
        }
        //发送 HTTP 请求,默认异步请求
        request.send();
    })
}
 
getJson("http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1")
    .then((resolve) => {
        console.log(resolve)
    })
    .catch((reject) => {
        console.log(reject)
    })

详细内容可参考:https://blog.csdn.net/MrJavaweb/article/details/79475949

Generator生成器函数

它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序

1. 声明
Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字

function* showWords() {
    yield 'one';
    yield 'two';
    return 'three';
}

var show = showWords();

show.next() // {done: false, value: "one"}
show.next() // {done: false, value: "two"}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

如上代码,定义了一个showWords的生成器函数,调用之后返回了一个迭代器对象(即show)
调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)
每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了

2. yield和yield*

有时候,我们会看到yield之后跟了一个星号,它是什么,有什么用呢?
类似于生成器前面的*号,yield后面的星号也跟生成器有关,举个例子:

function* showWords() {
    yield 'one';
    yield showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: showNumbers}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

增添了一个生成器函数,我们想在showWords中调用一次,简单的 yield showNumbers()之后发现并没有执行函数里面的yield 10+1

因为yield只能原封不动地返回右边运算后值,但现在的showNumbers()不是一般的函数调用,返回的是迭代器对象

所以换个yield* 让它自动遍历进该对象

function* showWords() {
    yield 'one';
    yield* showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: 11}
show.next() // {done: false, value: 12}
show.next() // {done: true, value: "three"}

要注意的是,这yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错

function showWords() {
    yield 'one'; // Uncaught SyntaxError: Unexpected string
}

虽然换成yield*不会直接报错,但使用的时候还是会有问题,因为’one’字符串中没有Iterator接口,没有yield提供遍历

function showWords() {
    yield* 'one'; 
}

var show = showWords();

show.next() // Uncaught ReferenceError: yield is not defined

在爬虫开发中,我们常常需要请求多个地址,为了保证顺序,引入Promise对象和Generator生成器函数,看这个简单的例子:

var urls = ['url1', 'url2', 'url3'];

function* request(urls) {
    urls.forEach(function(url) {
        yield req(url);
    });

//     for (var i = 0, j = urls.length; i < j; ++i) {
//         yield req(urls[i]);
//     }
}

var r = request(urls);
r.next();

function req(url) {
    var p = new Promise(function(resolve, reject) {
        $.get(url, function(rs) {
            resolve(rs);
        });
    });

    p.then(function() {
        r.next();
    }).catch(function() {

    });
}

上述代码中forEach遍历url数组,匿名函数内部不能使用yield关键字,改换成注释中的for循环就行了

3. next()调用中的传参
参数值有注入的功能,可改变上一个yield的返回值,如

function* showNumbers() {
    var one = yield 1;
    var two = yield 2 * one;
    yield 3 * two;
}

var show = showNumbers();

show.next().value // 1
show.next().value // NaN
show.next(2).value // 6

第一次调用next之后返回值one为1,但在第二次调用next的时候one其实是undefined的,因为generator不会自动保存相应变量值,我们需要手动的指定,这时two值为NaN,在第三次调用next的时候执行到yield 3 * two,通过传参将上次yield返回值two设为2,得到结果

另一个例子:

由于ajax请求涉及到网络,不好处理,这里用了setTimeout模拟ajax的请求返回,按顺序进行,并传递每次返回的数据

var urls = ['url1', 'url2', 'url3'];
 function* request(urls) {
     var data;
 
     for (var i = 0, j = urls.length; i < j; ++i) {
         data = yield req(urls[i], data);
     }
 }
 
 var r = request(urls);
 r.next();
 
 function log(url, data, cb) {
     setTimeout(function() {
         cb(url);
     }, 1000);
     
 }
 
 function req(url, data) {
     var p = new Promise(function(resolve, reject) {
         log(url, data, function(rs) {
             if (!rs) {
                 reject();
             } else {
                 resolve(rs);
            }
         });
     });
 
     p.then(function(data) {
         console.log(data);
         r.next(data);
     }).catch(function() {
         
     });
 }

达到了按顺序请求三个地址的效果,初始直接r.next()无参数,后续通过r.next(data)将data数据传入
在这里插入图片描述

注意代码的第16行,这里参数用了url变量,是为了和data数据做对比

因为初始next()没有参数,若是直接将url换成data的话,就会因为promise对象的数据判断 !rs == undefined 而reject

所以将第16行换成 cb(data || url);

在这里插入图片描述

通过模拟的ajax输出,可了解到next的传参值,第一次在log输出的是 url = 'url1’值,后续将data = 'url1’传入req请求,在log中输出 data = 'url1’值

4. for…of循环代替.next()

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

for (var n of show) {
    console.log(n) // 1 2
}

此外,处理for…of循环,具有调用迭代器接口的方法方式也可遍历生成器函数,如扩展运算符…的使用

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

[...show] // [1, 2, length: 2]

async-await

  1. es7新增的 async函数
    2. 格式
    async function aa(){
    await ‘任务1’
    await ‘任务2’
    }

    1. 问题: readFile(’./01-Promise.js’) 运行结果是Promise, 但是我们使用 async await之后, 它的结果是具体的数据了?

    分析: async函数使用了generator函数的语法糖 , 它直接生成对象 {value: ‘’,done:false} await 直接将value提取出来了

    实现: 将三层函数嵌套的第三层中的返回值返回来

    1. 扩展:
      多层函数嵌套(异步执行) , 我们想把里层函数,一般情况出现在数据请求,我们将请求得到的数据返回出来

      解决: Promise + async

const fs = require('fs')

const readFile = (filename) =>{
  return new Promise((resolve,reject)=>{
    fs.readFile(filename,(err,data)=>{
      resolve(data.toString())
    })
  })
}

const asyncFn = async() => {
  const f1 = await readFile('./01-Promise.js') // {value: '', done: false}
  // const f1 =  readFile('./01-Promise.js').then(data=>data)
  const f2 = await readFile('./02-generator.js')
  console.log( f1 )
  console.log( f2 )
}

asyncFn()

node.js中setImmediate()和Process.nextTick()

一、两者的区别

1.在理解两者的区别之前要说一下轮询

前面博客也有记录,nodejs中是事件驱动的,有一个循环线程一直从事件队列中取任务执行或者I/O的操作转给后台线程池来操作,把这个循环线程的每次执行的过程算是一次轮询.

2.setImmediate()的使用

即时计时器立即执行工作,它是在事件轮询之后执行,为了防止轮询阻塞,每次只会调用一个。

3.Process.nextTick()的使用

它和setImmediate()执行的顺序不一样,它是在事件轮询之前执行,为了防止I/O饥饿,所以有一个默认process.maxTickDepth=1000来限制事件队列的每次循环可执行的nextTick()事件的数目。

4.总结

在网上百度的关于它们的总结:

nextTick()的回调函数执行的优先级要高于setImmediate();

process.nextTick()属于idle观察者,setImmediate()属于check观察者.在每一轮循环检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者.

在具体实现上,process.nextTick()的回调函数保存在一个数组中,
setImmediate()的结果则是保存在链表中.
在行为上,process.nextTick()在每轮循环中会将数组中的回调函数全部执行完.
而setImmediate()在每轮循环中执行链表中的一个回调函数.

5.代码demo

//加入2个nextTick()的回调函数
process.nextTick(function(){
    console.log("nextTick延迟执行A");
});
process.nextTick(function(){
    console.log("nextTick延迟执行B");
    setImmediate(function(){
        console.log("setImmediate延迟执行C");
    });
    process.nextTick(function(){
        console.log("nextTick延迟执行D");
    });
});
//加入两个setImmediate()回调函数
setImmediate(function(){
    console.log("setImmediate延迟执行E");
    process.nextTick(function(){
        console.log("强势插入F");
    });
    setImmediate(function(){
        console.log("setImmediate延迟执行G");
    });
});
setImmediate(function(){
    console.log("setImmediate延迟执行H");
    process.nextTick(function(){
        console.log("强势插入I");
    });
    process.nextTick(function(){
        console.log("强势插入J");
    });
    setImmediate(function(){
        console.log("setImmediate延迟执行K");
    });
});
console.log("正常执行L");

运行结果

正常执行L
nextTick延迟执行A
nextTick延迟执行B
nextTick延迟执行D
setImmediate延迟执行E
setImmediate延迟执行H
setImmediate延迟执行C
强势插入F
强势插入I
强势插入J
setImmediate延迟执行G
setImmediate延迟执行K

Process finished with exit code 0

three-part-async

  1. 第三方的封装库
  2. 暴露了一个 async对象 , 这个对象身上有很多的api
  3. api (多任务执行)
    parallel
    series
    举例:
    async.parallel([
    function(callback){
    callback(null,‘任务1’)
    },
    function(callback){
    callback(null,‘任务2’)
    },
    ],(err,data)=>{
    console.log(‘data’,data)
    })
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值