由Blocking Search动画引出的知识点2

调用含有 setInterval 的函数时异步问题的解决

重点:使用 async/await 与 Promise 对象

在代码中引用

  • 引用前(有问题的代码)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    var linearSearch = function (desired_letter, array, flag) {
    ...
    linear_interval = setInterval(function () {
    ...
    if (current_index >= array.length) {
    clearInterval(linear_interval);
    return -1;
    }
    ...
    if (current_value >= desired_letter) {
    clearInterval(linear_interval);
    ...
    return current_index;
    }
    current_index += 1;
    }, SEARCH_INTERVAL_TIME/2);
    }

    // ## Blocking Search
    async function startBlockingSearch(desired_letter, array) {
    ...
    const which_block = linearSearch(desired_letter, block_arr, 0);
    console.log("which_block = ", which_block);
    ...
    }

这里遇到了 which_block 打印出来始终为 undefined 的问题。

  • 问题解决后的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    var linearSearch = function (desired_letter, array, flag) {
    return new Promise(function(resolve, reject){
    ...
    linear_interval = setInterval(function () {
    ...
    if (current_index >= array.length) {
    clearInterval(linear_interval);
    resolve(-1);
    }
    ...
    if (current_value >= desired_letter) {
    clearInterval(linear_interval);
    ...
    resolve(current_index);
    }
    ...
    }, SEARCH_INTERVAL_TIME/2);
    });
    }

    // ## Blocking Search
    async function startBlockingSearch(desired_letter, array) {
    ...
    const which_block = await linearSearch(desired_letter, block_arr, 0);
    console.log("which_block = ", which_block);
    ...
    }
  • 需要注意的地方

    • 在 async 的函数(这里是 startBlockingSearch 函数)里面引用的函数(这里是 linearSearch 函数)的真正返回值需要用 resolve 包裹起来
    • 且引用的函数必须再用 return new Promise(function(resolve, reject){…} 嵌套一次

关于Promise对象

一些参考链接

  • JS Promise API

    • 主要内容:
      Promise 三状态

      • pending:初始状态,未完成或拒绝
      • fulfilled:操作成功完成[Resolved(已完成,又称 Fulfilled)]
      • rejected:操作失败

        pending状态的Promise对象可能被填充了(fulfilled)值,也可能被某种理由(异常信息)拒绝(reject)了。当其中任一种情况出现时,Promise对象的then方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled和onrejected,它们都是Function类型。当值被填充时,调用then的onfulfilled方法,当Promise被拒绝时,调用then的onrejected方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)

        因为Promise.prototype.then和 Promise.prototype.catch方法返回 promises对象自身, 所以它们可以被链式调用.

        promise

      Promise API

      • Promise.resolve() 执行成功
      • Promise.reject() 执行失败
      • Promise.prototype.then() 递延处理
      • Promise.prototype.catch() 异常
  • 闲话Promise机制

  • Javascript 中的神器——Promise

    • 主要内容:
      封装代码与异步代码

      1
      2
      3
      4
      5
      6
      7
      new Promise(function (resolve, reject) {
      resolve(someValue);
      });

      写成

      Promise.resolve(someValue);

      捕获同步异常

      1
      2
      3
      4
      5
      new Promise(function (resolve, reject) {
      throw new Error('悲剧了,又出 bug 了');
      }).catch(function(err){
      console.log(err);
      });

关于async 与 await

一些参考链接

  • Javascript中的async await

    • 注意点:

      • async用来申明里面包裹的内容可以进行同步的方式执行,await则是进行执行顺序控制,每次执行一个await,程序都会暂停等待await返回值,然后再执行之后的await。
      • await后面调用的函数需要返回一个promise,另外这个函数是一个普通的函数即可,而不是generator。
      • await只能用在async函数之中,用在普通函数中会报错。

        1
        2
        3
        4
        5
        6
        7
        8
        async function dbFuc(db) {
        let docs = [{}, {}, {}];

        // 报错
        docs.forEach(function (doc) {
        await db.post(doc);
        });
        }

        上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。

        1
        2
        3
        4
        5
        6
        7
        8
        async function dbFuc(db) {
        let docs = [{}, {}, {}];

        // 可能得到错误结果
        docs.forEach(async function (doc) {
        await db.post(doc);
        });
        }

        上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。

        1
        2
        3
        4
        5
        6
        7
        async function dbFuc(db) {
        let docs = [{}, {}, {}];

        for (let doc of docs) {
        await db.post(doc);
        }
        }

        如果确实希望多个请求并发执行,可以使用 Promise.all 方法。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        async function dbFuc(db) {
        let docs = [{}, {}, {}];
        let promises = docs.map((doc) => db.post(doc));

        let results = await Promise.all(promises);
        console.log(results);
        }

        // 或者使用下面的写法

        async function dbFuc(db) {
        let docs = [{}, {}, {}];
        let promises = docs.map((doc) => db.post(doc));

        let results = [];
        for (let promise of promises) {
        results.push(await promise);
        }
        console.log(results);
        }
+ await命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}

// 另一种写法

async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}

实现效果

blockingSearch02.GIF

动态添加li的方法

1
2
3
4
5
6
7
8
9
for(i=0; i<len; i++) {
var node = document.createElement("LI");
var text = index_table_key[i].innerHTML;
var textnode = document.createTextNode(text);
block_arr.push(text);
node.setAttribute('id', text+'-linear');
node.appendChild(textnode);
document.getElementById('block-linear-search').appendChild(node);
}