异步之三:Promise+生成器 = async+await

1.打破完整运行

我们普遍都会依赖一个假定:一个函数一旦开始执行,就会运行到结束,期间不会有其他代码能够打断它并插入其间。不过ES6引入了一个新的函数类型,他并不符合这种运行到结束的特征,这类新的函数叫做生成器

javascript 复制代码
function *foo(){
    x++
    yield //在这里暂停代码
    console.log(x);
}
var x = 1

var it = foo()
it.next()
it.next()

上面的代码就是一个生成器foo。声明的方式是*foo,这是为了与普通函数区分开来。

var it = foo()这段代码并没有执行生成器,而是构造了一个迭代器(iterator),通过这个迭代器来控制来控制生成器的运行。

it.next()启动了生成器,代码运行到了第一个yield,即执行了x++这段代码,然后就停止了。

第二个it.next()才执行输出x。

这就是生成器,是一类特殊的函数,可以一次或多次启动和暂停,并不一定非得要完成。

1.输入和输出

生成器任然是一个函数,一些基本的特性没有改变,任然可以接受参数(输入)或者返回值(输出)

javascript 复制代码
function *foo(x,y){
    return x * y
}

var it = foo(6,7)
var res = it.next()
res.value //42

我们将foo(6,7)赋值给迭代器it,尽管看起来很熟悉,但是生成器foo并没有向普通函数一样实际运行。然后调用it.next(),指示生成器foo从当前位置开始继续运行,停在下一个yield处或者直到生成器结束。

这里的next()调用的结果是一个对象,他有一个value属性,持有从*foo()返回的值。换句话说,yield会导致生成器在执行过程中发送出一个值,这有点类似于return

javascript 复制代码
function *foo(x){
    var y = x * (yield 'hello') //yield的第一个值
    return y
}

var it = foo(6)

var res = it.next() //第一个next不传入任何东西
res.value //'hello'

res = it.next(7) //向等待的yield传入7
res.value //42

消息的传递是双向的,yield..作为一个表达式可以发出消息响应next()调用,next()调用也可以向暂停的yield表达式发送值。

第一个var res = it.next()遇到yield就会停止运行,并将yield后面的表达式传递给res。

第二个res = it.next(7)会将7传给暂停的yield,此时生成器运行到了最后,因此res的值实际上就是return的值

2.多个迭代器
ini 复制代码
function *foo(x){
   var x = yield 2
   z++
   var y = yield(x * z)
   console.log(x,y,z);
}
var z = 1

var it1 = foo()
var it2 = foo()

var val1 = it1.next().value //2,yield 2
var val2 = it2.next().value //2,yield 2

val1 = it1.next(val2 * 10).value //40,x:20;z:2
val1 = it1.next(val1 * 5).value //600,x:200;z:3

it1.next(val2 / 2) //y:300;20 322 3
it2.next(val1 / 4) //y:10;200 10 3

迭代器仍然是函数,每一个迭代器内部的值不会互相干扰

生成器+Promise,异步迭代生成器

回顾一下Ajax例子中基于Promise的实现方法

javascript 复制代码
function request(url){
    return new Promise((resolve, reject) => {
        ajax(url,resolve)
    })
}

function foo(x,y){
    return request(
        `http://some.url.1?x=${x}&y=${y}`
    )
}

foo(11,31)
.then(
    function(text){
        console.log(text);
    },
    function(err){
        console.log(err);
    }
)

这里支持Promise的foo函数在发出Ajax调用后返回了一个Promise,这暗示我们可以通过foo构造一个Promise,并且通过生成器把它yield出来,然后通过迭代器控制代码就可以接受到这个Promise。

但是迭代器应该对这个Promise做些什么呢?

他应该侦听这个Promise的决议(完成或拒绝),然后要么使用完成信息恢复生成器运行,要么向生成器抛出一个带有拒绝原因的错误。这就是Promise和生成器的最大效用。

javascript 复制代码
function request(url){
    return new Promise((resolve, reject) => {
        ajax(url,resolve)
    })
}

function foo(x,y){
    return request(
        `http://some.url.1?x=${x}&y=${y}`
    )
}

function *main(){
    try {
        var text = yield foo(11,13)
    } catch (error) {
        console.error(err)
    }
}

var it = main()
var p = it.next().value

// 等待Promise决议
p.then(
    function(text){
        it.next(text)
    },
    function(err){
        it.throw(err)
    }
)

我们发现,*main生成器中的代码完全不需要改变,在生成器内部,不管什么值yield出来,都只是一个透明的细节,我们甚至没有意识到其发生。

我们需要关注的额,是怎么运行*main,首先通过var it = main()开启生成器,再通过var p = it.next().value获得foo返回的Promise并赋值给p,此时的p还是未决议状态,一旦Promise完成了,就会调用it.next()或者it.throw()执行生成器下一步。

当然,我们不可能为每一个生成器都编写不同的Promise链,如果有一个工具为我们实现的话该会有多好呀!

aysnc和await

前面的模式--生成器yield出Promise,然后通过控制生成器的迭代器来执行它,直到结束。这种写法想必大家都想到了async和await,是的没错,与其说async和await是Promise的语法糖,我更喜欢说他们是Promise+await是Promise+生成器的语法糖,他们帮我们省略了为每一个迭代器添加Promise链。

如果你await了一个Promise,async函数就会自动获知要做什么,他会暂停这个函数,直到Promise决议(这是异步过程,await后面的代码会放入微任务队列)。

相关推荐
秦时明月之君临天下2 分钟前
React和Next.js的相关内容
前端·javascript·react.js
上官花雨34 分钟前
什么是axios?怎么使用axios封装Ajax?
前端·ajax·okhttp
米奇妙妙wuu35 分钟前
React中 setState 是同步的还是异步的?调和阶段 setState 干了什么?
前端·javascript·react.js
李刚大人38 分钟前
react-amap海量点优化
前端·react.js·前端框架
闹闹没有闹1 小时前
socket连接封装
前端
qq_364371722 小时前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森3 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
new出一个对象6 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥7 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森8 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3