第6章 未来的函数:生成器和promise

1. 生成器函数

1.1 定义生成器函数

// 在关键字function后面添加 * 定义生成器函数
function* newGenerator() {
    // ...
    // 在生成器内部使用yield生成独立的值
    yield "One";
    yield "Two";
    // ...
}
// 调用生成器函数会创建一个迭代器(iterator)对象
let result = newGenerator();
console.log(typeof result === "object");
// true

1.2 迭代器对象

 function* newGenerator() {
    // ...
    yield "One";
    yield "Two";
    // ...
}
// 调用生成器函数创建迭代器函数
const result = newGenerator();
console.log(typeof result === "object");
// true

// 显式调用迭代器中的next方法依次取出迭代器中的值
const r1 = result.next();
console.log(r1);
// {value: "One", done: false}
// next方法返回的对象包含两个属性:
// value为函数的返回值,done表示迭代器函数是否已经完成

const r2 = result.next();
console.log(r2);
// {value: "Two", done: false}

const r3 = result.next();
console.log(r3);
// {value: undefined, done: true}
// 生成器的值全部返回后,value的值为undefined,done为true

1.3 对迭代器进行迭代

function* newGenerator() {
    // ...
    yield "One";
    yield "Two";
    // ...
}

const result = newGenerator();
let item;       // 创建变量保存生成器产生的单个值
// 通过done属性判断生成器是否完成
while(!(item = result.next()).done) {
    console.log(item.value);
    // One
    // Two
}

上述while循环是for-of循环的实现原理。for-of只是对迭代器进行迭代的语法糖

function* newGenerator() {
    // ...
    yield "One";
    yield "Two";
    // ...
}

for(let value of newGenerator()) {
    console.log(value);
    // One
    // Two
}

1.4 把执行权交给下一个生成器

function* newGenerator() {
    yield "One";
    // yield* 将执行权交给了另一个生成器
    // 类似于在普通函数中调用另外一个函数
    yield* anotherGenerator();
    yield "Two";
}

function* anotherGenerator() {
    yield "Wango";
    yield "Lily";
}

const STRING = [];
for(let value of newGenerator()) {
    STRING.push(value);
}

console.log(STRING);
// ["One", "Wango", "Lily", "Two"]

2. 使用生成器

2.1 用生成器生成ID

function* idGenerator() {
    let id = 0;
    while(true) {
        yield ++id;
    }
}

const idIterator = idGenerator();

const per1 = {id: idIterator.next().value};
const per2 = {id: idIterator.next().value};
const per3 = {id: idIterator.next().value};

console.log(per1.id);       // 1
console.log(per2.id);       // 2
console.log(per3.id);       // 3

2.2 用迭代器遍历DOM树

<div id="subTree">
    <form>
        <input type="text">
    </form>
    <p>Paragraph</p>
    <span>Span</span>
</div>
<script>
    // 递归遍历DOM
    function traverseDOM(elem, callback) {
        callback(elem);
        elem = elem.firstElementChild;
        while(elem) {
            traverseDOM(elem, callback);
            elem = elem.nextElementSibling;
        }
    }

    const subTree = document.getElementById("subTree");
    traverseDOM(subTree, function(elem){
        console.log(elem.nodeName);
    });

    // 生成器遍历DOM,以迭代的方式书写概念上递归的代码
    function* DomTraversal(elem) {
        yield elem;
        elem = elem.firstElementChild;
        while(elem) {
            yield* DomTraversal(elem);
            elem = elem.nextElementSibling;
        }
    }

    const subTree = document.getElementById("subTree");
    for(let elem of DomTraversal(subTree)) {
        console.log(elem.nodeName);
    }
</script>

3. 与生成器交互

3.1 用参数和next方法发送值

// 以下代码仅作演示用,不具备任何实际意义

// 生成器函数也可以接收参数
function* NumGenerator(num) {
    const NUM = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
    while(true) {
        // yield向外部返回一个对象的同时,可以从next方法接收一个新值
        num = yield NUM[num];
    }
}

const numIterator = NumGenerator(1);

const num1 = numIterator.next();
const num2 = numIterator.next(5);
const num3 = numIterator.next(9);

console.log(num1.value);        // 壹
console.log(num2.value);        // 伍
console.log(num3.value);        // 玖

next方法为等待中的yield表达式提供了值,所以,如果没有等待中的yield表达式,也就没有什么值能应用的。基于这个原因,我们无法通过第一次调用next方法来向生成器提供该值。但如果需要为生成器提供一个初始值,可以调用生成器自身,就像上述中的const numIterator = NumGenerator(1);

3.2向生成器抛出异常以传递值

function* NewGenerator() {
    try {
        yield "First call";
    }
    catch (e) {
        console.log("接收值:" + e);
    }
}

const newIterator = NewGenerator();

const num1 = newIterator.next();
console.log(num1.value);
// First call

// 通过在所有迭代器上都有效的throw方法,向生成器抛出异常并夹带一个值
const num2 = newIterator.throw("Wango");
// 接收值:Wango

4. promise

  • promise对象是对我们现在尚未得到但将来会得到值的占位符;它是对我们最终能够得知异步计算结果的一种保证。如果我们兑现了我们的承诺,那结果会得到一个值。如果发生了问题,结果则是一个错误,一个为什么不能交付的借口。使用promise的最佳案例是从服务器获取数据。

4.1 创建一个简单的promise

// 通过内置Promise构造函数创建promise对象
// 构造函数接收一个函数(即执行函数),这个函数接收两个函数参数(均为内置函数)
const dataPromise = new Promise((resolve, reject) => {
    // 这部分代码会被立即执行
    
    // 调用resolve函数,传入最终数据,表示一个promise将被成功兑现
    resolve("Wango");

    // 调用reject则promise被拒绝
    // reject("An error");
});

// then方法接收两个回调函数参数,promise成功兑现后会调用第一个回调函数
// 出现错误则调用第二个回调函数
// 这两个回调函数总是会异步调用
dataPromise.then(data => {
    // 回调函数参数data是resolve传递过来的最终数据
    console.log(data);
    // Wango
}, err => {
    // err是reject传递过来的数据
    console.log(err);
});

4.2 简单回调函数所带来的问题

  • 回调函数发生错误时,无法用内置语言结构来处理,导致错误经常丢失
  • 回调函数执行连续步骤非常棘手,需要嵌套一堆回调函数
  • 回调函数执行很多并行任务也很棘手,需要书写很多样板代码用于并行执行多个任务

4.3 深入promise

promise的执行顺序

const dataPromise = new Promise((resolve, reject) => {
    console.log("Processing dataPromise");
    setTimeout(() => {
        resolve("dataPromise resolved");
    }, 1000);
});

dataPromise.then(data => {
    console.log(data);
}, err => {
    console.log(err);
});

const anotherPromise = new Promise((resolve, reject) => {
    console.log("Processing anotherPromise");
    resolve("anotherPromise resolved");
});

anotherPromise.then(data => {
    console.log(data);
});
console.log("At cold end");
// Processing dataPromise
// Processing anotherPromise
// At cold end
// anotherPromise resolved
// dataPromise resolved

Promise构造函数在定义之后按先后顺心立即执行,而then方法会在promise兑现成功(或失败)后按完成时间先后执行(异步)

4.4 拒绝promise

  • 显式拒绝(调用reject方法)
const dataPromise = new Promise((resolve, reject) => {
    // 显式拒绝
    reject("An error");
});

dataPromise.then(
    data => console.log(data),
    err => console.log(err)     // An error
);
  • 隐式拒绝(抛出了异常)
const dataPromise = new Promise((resolve, reject) => {
    const NUM = 10;
    // 隐式拒绝,给const变量重新赋值抛出错误
    NUM = 100;
});

dataPromise.then(
    data => console.log(data)
).catch(    // 这里的链式调用catch接收一个错误处理函数,
            // 与将函数写在then第二个参数的效果一样
    err => console.log(err)
    // TypeError: Assignment to constant variable.
);

4.5 创建一个真实promise案例

function getJSON(url) {
    return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest();

        // 初始化请求
        request.open("GET", url);

        // 当服务器响应后会被调用
        request.onload = function () {
            // JSON代码容易出现语法错误,所以把对JSON.parse抱在try-catch中
            try {
                // 服务器状态码为200表示一切正常
                if (this.status === 200) {
                    resolve(JSON.parse(this.response));
                } else {
                    reject(this.status + " " + this.statusText);
                }
            } catch (e) {
                reject(e.message);
            }

            // 和服务器通信过程中发生错误后会被调用
            request.onerror = function () {
                reject(this.status + " " + this.statusText);
            }
        }

        // 发送请求
        request.send();
    });
};

getJSON("./students.json").then(
    data => {
        // 数据处理
    }
).catch(err => console.log(err));

本例中有3个潜在的错误源:客户端与服务器之间的连接错误、服务器返回错误的数据(无效响应状态码)、无效的JSON代码

4.6 链式调用promise处理相互依赖的异步任务序列

// 每次调用getJSON都会返回一个promise对象,
// 因此可以链式调用then,以顺序执行多个步骤
getJSON(url)
    .then(() => {})
    .then(() => {})
    .then(() => {})
    .catch(() => {});
// catch可以捕获任何步骤中产生的错误

4.7 Promise.all处理多个独立的异步任务

// Promise.all接收一个promise对象数组
// 其中只要一个被拒绝,则所有被拒绝
Promise.all([getJSON("./data/students.json"),
             getJSON("./data/teachers.json"),
             getJSON("./data/classes.json")])
             .then(results => {
             // 结果以数组的形式顺序返回
                 const students = results[0];
                 const teachers = results[1];
                 const classes = results[2];
                 console.log(students !== null);
                 console.log(teachers !== null);
                 console.log(classes !== null);
             }).catch(err => console.log(err));

4.8 Promise.race处理第一个成功(或失败)的promise

// 方法返回一个全新的promise对象
// 一旦数组中某一个promise被处理或被拒绝,
// 这个返回的promise同样会被处理或被拒绝
Promise.race([getJSON("./data/students.json"),
              getJSON("./data/teachers.json"),
              getJSON("./data/classes.json")]).then(result => {
                  console.log(result);
              }).catch(err => console.log(err));

5. 把生成器和promise相结合

// 仅作演示,不推荐使用
function async(genertor){
    // 创建迭代器控制生成器
    const iterator = genertor();

    // 处理生成器产生的值
    function handle(iteratorResult){

        // 没有新值产生就直接返回
        if(iteratorResult.done) { return; }

        const iteratorValue = iteratorResult.value;

        if(iteratorValue instanceof Promise){
            // 处理生成器返回的promise对象,
            // 用next方法发送数据给生成器并处理下一个返回的promise对象
            iteratorValue.then(res => handle(iterator.next(res)))
                         .catch(err => iterator.throw(err));
        }
    }

    try {
        handle(iterator.next());
    }
    catch(err) {iterator.throw(e)}
}

async(function* () {
    try {
        // 等待异步结果返回时暂停
        // 对每个异步任务执行yield
        const classes = yield getJSON("./data/classes.json");
        const teachers = yield getJSON(classes.classes[0].teachers);
        const students = yield getJSON(teachers.teachers[0].students);
        // 处理数据。。。 
    }
    catch (err) {console.log(err)};
});

6. 面向未来的async函数

async是ES8新增特性,本书并未详细讲解

// async关键字表明当前函数依赖一个异步返回的值
(async function(){
    try {
        // 每一个调用异步任务的位置上,都要放置await关键字,
        // 来告诉JS引擎,在不阻塞应用执行的情况下在这个位置等待执行结果
        const classes = await getJSON("./data/classes.json");
        const teachers = await getJSON(classes.classes[0].teachers);
        
        console.log(teachers.teachers[0].subject);
    }
    catch(e) {console.log(e)}
})();
原文地址:https://www.cnblogs.com/hycstar/p/14020109.html