全球彩票平台_全球彩票注册平台|官网下载地址

热门关键词: 全球彩票平台,全球彩票注册平台,全球彩官网下载地址

【全球彩官网下载地址】js之异步流动调查控

浅谈Node.js之异步流动调查整,浅谈node.js异步

前言

在一向不深度应用函数回调的经验的时候,去看这么些剧情照旧有少数艰巨的。由于Node.js独特的异步本性,才现身了“回调地狱”的标题,那篇作品中,笔者相比较详细的记录了什么样缓慢解决异步流难点。

作品会非常短,並且那篇是对异步流情势的疏解。文中会选用一个粗略的互联网蜘蛛的事例,它的作用是抓取钦点U福特ExplorerL的网页内容并保存在品种中,在篇章的末段,能够找到整篇小说中的源码demo。

1.原生JavaScript模式

本篇不对准初学者,因而会省略掉超越三分之二的基本功内容的批注:

(spider_v1.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function spider(url, callback) {
  const filename = utilities.urlToFilename(url);
  console.log(`filename: ${filename}`);

  fs.exists(filename, exists => {
    if (!exists) {
      console.log(`Downloading ${url}`);

      request(url, (err, response, body) => {
        if (err) {
          callback(err);
        } else {
          mkdirp(path.dirname(filename), err => {
            if (err) {
              callback(err);
            } else {
              fs.writeFile(filename, body, err => {
                if (err) {
                  callback(err);
                } else {
                  callback(null, filename, true);
                }
              });
            }
          });
        }
      });
    } else {
      callback(null, filename, false);
    }
  });
}

spider(process.argv[2], (err, filename, downloaded) => {
  if (err) {
    console.log(err);
  } else if (downloaded) {
    console.log(`Completed the download of ${filename}`);
  } else {
    console.log(`${filename} was already downloaded`);
  }
});

下面的代码的流水生产线大概是这般的:

  1. 把url转换成filename
  2. 判定该公文名是还是不是存在,若存在直接重返,不然步入下一步
  3. 发请求,获取body
  4. 把body写入到文件中

这是多个特别轻易版本的蜘蛛,他只可以抓取贰个url的从头到尾的经过,看到上边的回调多么让人发烧。那么大家初叶举行优化。

第一,if else 这种方法能够张开优化,那一个非常粗大略,不用多说,放二个相对来说效果:

/// before
if (err) {
  callback(err);
} else {
  callback(null, filename, true);
}

/// after
if (err) {
  return callback(err);
}
callback(null, filename, true);

代码这么写,嵌套就能够少一层,但经验丰盛的程序员会感觉,那样写过重强调了error,我们编制程序的最首要应该投身管理准确的多少上,在可读性上也设有这么的渴求。

另贰个优化是函数拆分,上边代码中的spider函数中,能够把下载文件和保存文件拆分出去。

(spider_v2.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function saveFile(filename, contents, callback) {
  mkdirp(path.dirname(filename), err => {
    if (err) {
      return callback(err);
    }
    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename, callback) {
  console.log(`Downloading ${url}`);

  request(url, (err, response, body) => {
    if (err) {
      return callback(err);
    }
    saveFile(filename, body, err => {
      if (err) {
        return callback(err);
      }
      console.log(`Downloaded and saved: ${url}`);
      callback(null, body);
    });
  })
}

function spider(url, callback) {
  const filename = utilities.urlToFilename(url);
  console.log(`filename: ${filename}`);

  fs.exists(filename, exists => {
    if (exists) {
      return callback(null, filename, false);
    }
    download(url, filename, err => {
      if (err) {
        return callback(err);
      }
      callback(null, filename, true);
    })
  });
}

spider(process.argv[2], (err, filename, downloaded) => {
  if (err) {
    console.log(err);
  } else if (downloaded) {
    console.log(`Completed the download of ${filename}`);
  } else {
    console.log(`${filename} was already downloaded`);
  }
});

下面的代码基本上是使用原生优化后的结果,但以此蜘蛛的功效太过轻巧,我们以往要求抓取有些网页中的全部url,那样才会引申出串行和相互的难点。

(spider_v3.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function saveFile(filename, contents, callback) {
  mkdirp(path.dirname(filename), err => {
    if (err) {
      return callback(err);
    }
    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename, callback) {
  console.log(`Downloading ${url}`);

  request(url, (err, response, body) => {
    if (err) {
      return callback(err);
    }
    saveFile(filename, body, err => {
      if (err) {
        return callback(err);
      }
      console.log(`Downloaded and saved: ${url}`);
      callback(null, body);
    });
  })
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }

  const links = utilities.getPageLinks(currentUrl, body);

  function iterate(index) {
    if (index === links.length) {
      return callback();
    }
    spider(links[index], nesting - 1, err => {
      if (err) {
        return callback(err);
      }
      iterate((index   1));
    })
  }

  iterate(0);
}

function spider(url, nesting, callback) {
  const filename = utilities.urlToFilename(url);

  fs.readFile(filename, "utf8", (err, body) => {
    if (err) {
      if (err.code !== 'ENOENT') {
        return callback(err);
      }
      return download(url, filename, (err, body) => {
        if (err) {
          return callback(err);
        }
        spiderLinks(url, body, nesting, callback);
      });
    }

    spiderLinks(url, body, nesting, callback);
  });
}

spider(process.argv[2], 2, (err, filename, downloaded) => {
  if (err) {
    console.log(err);
  } else if (downloaded) {
    console.log(`Completed the download of ${filename}`);
  } else {
    console.log(`${filename} was already downloaded`);
  }
});

上边的代码比较在此之前的代码多了七个宗旨功用,首先是通过帮助类获取到了有个别body中的links:

const links = utilities.getPageLinks(currentUrl, body);

内部贯彻就不解释了,另一个着力代码正是:

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }

  const links = utilities.getPageLinks(currentUrl, body);

  function iterate(index) {
    if (index === links.length) {
      return callback();
    }
    spider(links[index], nesting - 1, err => {
      if (err) {
        return callback(err);
      }
      iterate((index   1));
    })
  }

  iterate(0);
}

能够说下边这一小段代码,就是选拔原生达成异步串行的pattern了。除了这一个之外,还引进了nesting的定义,通过那是以此性情,能够垄断抓取档案的次序。

到此处我们就总体的落到实处了串行的功力,思量到质量,大家要开荒并行抓取的效果。

(spider_v4.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function saveFile(filename, contents, callback) {
  mkdirp(path.dirname(filename), err => {
    if (err) {
      return callback(err);
    }
    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename, callback) {
  console.log(`Downloading ${url}`);

  request(url, (err, response, body) => {
    if (err) {
      return callback(err);
    }
    saveFile(filename, body, err => {
      if (err) {
        return callback(err);
      }
      console.log(`Downloaded and saved: ${url}`);
      callback(null, body);
    });
  })
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }

  const links = utilities.getPageLinks(currentUrl, body);
  if (links.length === 0) {
    return process.nextTick(callback);
  }

  let completed = 0, hasErrors = false;

  function done(err) {
    if (err) {
      hasErrors = true;
      return callback(err);
    }

    if (  completed === links.length && !hasErrors) {
      return callback();
    }
  }

  links.forEach(link => {
    spider(link, nesting - 1, done);
  });
}

const spidering = new Map();

function spider(url, nesting, callback) {
  if (spidering.has(url)) {
    return process.nextTick(callback);
  }

  spidering.set(url, true);

  const filename = utilities.urlToFilename(url);

  /// In this pattern, there will be some issues.
  /// Possible problems to download the same url again and again。
  fs.readFile(filename, "utf8", (err, body) => {
    if (err) {
      if (err.code !== 'ENOENT') {
        return callback(err);
      }
      return download(url, filename, (err, body) => {
        if (err) {
          return callback(err);
        }
        spiderLinks(url, body, nesting, callback);
      });
    }

    spiderLinks(url, body, nesting, callback);
  });
}

spider(process.argv[2], 2, (err, filename, downloaded) => {
  if (err) {
    console.log(err);
  } else if (downloaded) {
    console.log(`Completed the download of ${filename}`);
  } else {
    console.log(`${filename} was already downloaded`);
  }
});

这段代码一样很简短,也许有八个宗旨内容。一个是什么兑现产出:

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }

  const links = utilities.getPageLinks(currentUrl, body);
  if (links.length === 0) {
    return process.nextTick(callback);
  }

  let completed = 0, hasErrors = false;

  function done(err) {
    if (err) {
      hasErrors = true;
      return callback(err);
    }

    if (  completed === links.length && !hasErrors) {
      return callback();
    }
  }

  links.forEach(link => {
    spider(link, nesting - 1, done);
  });
}

上边的代码能够说是兑现产出的叁个pattern。利用循环遍历来完成。另三个主干是,既然是出新的,那么利用 fs.exists 就能设分外,恐怕会再也下载同一文件,这里的缓慢解决方案是:

  • 接纳Map缓存某一url,url应该作为key

当今我们又有了新的必要,要求范围相同的时候出现的最大数,那么在此间就引入了二个自个儿感到最关键的定义:队列。

(task-Queue.js)

class TaskQueue {
  constructor(concurrency) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  pushTask(task) {
    this.queue.push(task);
    this.next();
  }

  next() {
    while (this.running < this.concurrency && this.queue.length) {
      const task = this.queue.shift();
      task(() => {
        this.running--;
        this.next();
      });
      this.running  ;
    }
  }
}

module.exports = TaskQueue;

上边的代码正是队列的贯彻代码,宗旨是 next() 方法,能够看到,当task插手队列中后,会立刻推行,那不是说这些职分一定立刻试行,而是指的是next会立即调用。

(spider_v5.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");
const TaskQueue = require("./task-Queue");
const downloadQueue = new TaskQueue(2);

function saveFile(filename, contents, callback) {
  mkdirp(path.dirname(filename), err => {
    if (err) {
      return callback(err);
    }
    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename, callback) {
  console.log(`Downloading ${url}`);

  request(url, (err, response, body) => {
    if (err) {
      return callback(err);
    }
    saveFile(filename, body, err => {
      if (err) {
        return callback(err);
      }
      console.log(`Downloaded and saved: ${url}`);
      callback(null, body);
    });
  })
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }

  const links = utilities.getPageLinks(currentUrl, body);
  if (links.length === 0) {
    return process.nextTick(callback);
  }

  let completed = 0, hasErrors = false;

  links.forEach(link => {
    /// 给队列出传递一个任务,这个任务首先是一个函数,其次该函数接受一个参数
    /// 当调用任务时,触发该函数,然后给函数传递一个参数,告诉该函数在任务结束时干什么
    downloadQueue.pushTask(done => {
      spider(link, nesting - 1, err => {
        /// 这里表示,只要发生错误,队列就会退出
        if (err) {
          hasErrors = true;
          return callback(err);
        }
        if (  completed === links.length && !hasErrors) {
          callback();
        }

        done();
      });
    });

  });
}

const spidering = new Map();

function spider(url, nesting, callback) {
  if (spidering.has(url)) {
    return process.nextTick(callback);
  }

  spidering.set(url, true);

  const filename = utilities.urlToFilename(url);

  /// In this pattern, there will be some issues.
  /// Possible problems to download the same url again and again。
  fs.readFile(filename, "utf8", (err, body) => {
    if (err) {
      if (err.code !== 'ENOENT') {
        return callback(err);
      }
      return download(url, filename, (err, body) => {
        if (err) {
          return callback(err);
        }
        spiderLinks(url, body, nesting, callback);
      });
    }

    spiderLinks(url, body, nesting, callback);
  });
}

spider(process.argv[2], 2, (err, filename, downloaded) => {
  if (err) {
    console.log(`error: ${err}`);
  } else if (downloaded) {
    console.log(`Completed the download of ${filename}`);
  } else {
    console.log(`${filename} was already downloaded`);
  }
});

因而,为了限制并发的个数,只需在 spiderLinks 方法中,把task遍历归入队列就足以了。那绝对来讲很简短。

到此处结束,大家利用原生JavaScript达成了多少个有相对完好意义的互连网蜘蛛,不仅可以串行,也能冒出,仍是可以够调控并发个数。

2.使用async库

把区别的成效放到不相同的函数中,会给我们带来巨大的好处,async库十二分流行,它的性质也不错,它里面基于callback。

(spider_v6.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");
const series = require("async/series");
const eachSeries = require("async/eachSeries");

function download(url, filename, callback) {
  console.log(`Downloading ${url}`);

  let body;

  series([
    callback => {
      request(url, (err, response, resBody) => {
        if (err) {
          return callback(err);
        }
        body = resBody;
        callback();
      });
    },
    mkdirp.bind(null, path.dirname(filename)),
    callback => {
      fs.writeFile(filename, body, callback);
    }
  ], err => {
    if (err) {
      return callback(err);
    }
    console.log(`Downloaded and saved: ${url}`);
    callback(null, body);
  });
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
  if (nesting === 0) {
    return process.nextTick(callback);
  }

  const links = utilities.getPageLinks(currentUrl, body);
  if (links.length === 0) {
    return process.nextTick(callback);
  }

  eachSeries(links, (link, cb) => {
    "use strict";
    spider(link, nesting - 1, cb);
  }, callback);
}

const spidering = new Map();

function spider(url, nesting, callback) {
  if (spidering.has(url)) {
    return process.nextTick(callback);
  }

  spidering.set(url, true);

  const filename = utilities.urlToFilename(url);

  fs.readFile(filename, "utf8", (err, body) => {
    if (err) {
      if (err.code !== 'ENOENT') {
        return callback(err);
      }
      return download(url, filename, (err, body) => {
        if (err) {
          return callback(err);
        }
        spiderLinks(url, body, nesting, callback);
      });
    }

    spiderLinks(url, body, nesting, callback);
  });
}

spider(process.argv[2], 1, (err, filename, downloaded) => {
  if (err) {
    console.log(err);
  } else if (downloaded) {
    console.log(`Completed the download of ${filename}`);
  } else {
    console.log(`${filename} was already downloaded`);
  }
});

在下面的代码中,咱们只行使了async的三个功效:

const series = require("async/series"); // 串行
const eachSeries = require("async/eachSeries"); // 并行
const queue = require("async/queue"); // 队列

是因为相比轻巧,就不做解释了。async中的队列的代码在(spider_v7.js)中,和上边咱们自定义的队列很相像,也不做越来越多解释了。

3.Promise

Promise是二个磋商,有相当多库完毕了那些左券,大家用的是ES6的落到实处。轻松的话promise正是贰个约定,假诺变成了,就调用它的resolve方法,战败了就调用它的reject方法。它内有落实了then方法,then再次来到promise本身,那样就变成了调用链。

实际Promise的原委有众多,在实际上选取中是怎么着把一般的函数promise化。那上头的剧情在这里也不讲了,笔者本身也远远不够格

(spider_v8.js)

const utilities = require("./utilities");
const request = utilities.promisify(require("request"));
const fs = require("fs");
const readFile = utilities.promisify(fs.readFile);
const writeFile = utilities.promisify(fs.writeFile);
const mkdirp = utilities.promisify(require("mkdirp"));
const path = require("path");


function saveFile(filename, contents, callback) {
  mkdirp(path.dirname(filename), err => {
    if (err) {
      return callback(err);
    }
    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename) {
  console.log(`Downloading ${url}`);

  let body;

  return request(url)
    .then(response => {
      "use strict";
      body = response.body;
      return mkdirp(path.dirname(filename));
    })
    .then(() => writeFile(filename, body))
    .then(() => {
      "use strict";
      console.log(`Downloaded adn saved: ${url}`);
      return body;
    });
}

/// promise编程的本质就是为了解决在函数中设置回调函数的问题
/// 通过中间层promise来实现异步函数同步化
function spiderLinks(currentUrl, body, nesting) {
  let promise = Promise.resolve();
  if (nesting === 0) {
    return promise;
  }

  const links = utilities.getPageLinks(currentUrl, body);

  links.forEach(link => {
    "use strict";
    promise = promise.then(() => spider(link, nesting - 1));
  });

  return promise;
}

function spider(url, nesting) {
  const filename = utilities.urlToFilename(url);

  return readFile(filename, "utf8")
    .then(
      body => spiderLinks(url, body, nesting),
      err => {
        "use strict";
        if (err.code !== 'ENOENT') {
          /// 抛出错误,这个方便与在整个异步链的最后通过呢catch来捕获这个链中的错误
          throw err;
        }
        return download(url, filename)
          .then(body => spiderLinks(url, body, nesting));
      }
    );
}

spider(process.argv[2], 1)
  .then(() => {
    "use strict";
    console.log('Download complete');
  })
  .catch(err => {
    "use strict";
    console.log(err);
  });

能够见到上边的代码中的函数都以从未callback的,只要求在终极catch即可了。

在统一筹算api的时候,应该帮衬两种格局,及匡助callback,又协助promise

function asyncDivision(dividend, divisor, cb) {
  return new Promise((resolve, reject) => {
    "use strict";
    process.nextTick(() => {
      const result = dividend / divisor;
      if (isNaN(result) || !Number.isFinite(result)) {
        const error = new Error("Invalid operands");
        if (cb) {
          cb(error);
        }
        return reject(error);
      }

      if (cb) {
        cb(null, result);
      }
      resolve(result);
    });
  });
}

asyncDivision(10, 2, (err, result) => {
  "use strict";
  if (err) {
    return console.log(err);
  }
  console.log(result);
});

asyncDivision(22, 11)
  .then((result) => console.log(result))
  .catch((err) => console.log(err));

4.Generator

Generator很风趣,他得以让暂停函数和苏醒函数,利用thunkify和co那三个库,大家上边包车型地铁代码实现起来特别酷。

(spider_v9.js)

const thunkify = require("thunkify");
const co = require("co");
const path = require("path");
const utilities = require("./utilities");

const request = thunkify(require("request"));
const fs = require("fs");
const mkdirp = thunkify(require("mkdirp"));
const readFile = thunkify(fs.readFile);
const writeFile = thunkify(fs.writeFile);
const nextTick = thunkify(process.nextTick);

function* download(url, filename) {
  console.log(`Downloading ${url}`);

  const response = yield request(url);
  console.log(response);

  const body = response[1];
  yield mkdirp(path.dirname(filename));

  yield writeFile(filename, body);

  console.log(`Downloaded and saved ${url}`);
  return body;
}

function* spider(url, nesting) {
  const filename = utilities.urlToFilename(url);

  let body;

  try {
    body = yield readFile(filename, "utf8");
  } catch (err) {
    if (err.code !== 'ENOENT') {
      throw err;
    }
    body = yield download(url, filename);
  }

  yield spiderLinks(url, body, nesting);
}

function* spiderLinks(currentUrl, body, nesting) {
  if (nesting === 0) {
    return nextTick();
  }

  const links = utilities.getPageLinks(currentUrl, body);

  for (let i = 0; i < links.length; i  ) {
    yield spider(links[i], nesting - 1);
  }
}

/// 通过co就自动处理了回调函数,直接返回了回调函数中的参数,把这些参数放到一个数组中,但是去掉了err信息
co(function* () {
  try {
    yield spider(process.argv[2], 1);
    console.log('Download complete');
  } catch (err) {
    console.log(err);
  }
});

总结

自身并从未写promise和generator并发的代码。以上这个内容来自于那本书nodejs-design-patterns 。

demo下载

以上正是本文的全体内容,希望对大家的求学抱有支持,也愿意我们多多扶助帮客之家。

前言 在一贯不深度应用函数回调的经历的时候,去看这个内容依旧有少数高难的。由于Node.js独特的...

(spider_v9.js)

3.Promise

Promise是多少个商量,有无数库实现了那些合同,我们用的是ES6的落到实处。轻松的话promise正是三个约定,借使形成了,就调用它的resolve方法,战败了就调用它的reject方法。它内有落到实处了then方法,then重回promise本身,那样就造成了调用链。

实际Promise的从头到尾的经过有好些个,在实际上利用中是怎么着把一般的函数promise化。那下边包车型地铁剧情在此间也不讲了,作者要好也远远不足格

(spider_v8.js)

const utilities = require("./utilities");
const request = utilities.promisify(require("request"));
const fs = require("fs");
const readFile = utilities.promisify(fs.readFile);
const writeFile = utilities.promisify(fs.writeFile);
const mkdirp = utilities.promisify(require("mkdirp"));
const path = require("path");


function saveFile(filename, contents, callback) {
    mkdirp(path.dirname(filename), err => {
        if (err) {
            return callback(err);
        }
        fs.writeFile(filename, contents, callback);
    });
}

function download(url, filename) {
    console.log(`Downloading ${url}`);

    let body;

    return request(url)
        .then(response => {
            "use strict";
            body = response.body;
            return mkdirp(path.dirname(filename));
        })
        .then(() => writeFile(filename, body))
        .then(() => {
            "use strict";
            console.log(`Downloaded adn saved: ${url}`);
            return body;
        });
}

/// promise编程的本质就是为了解决在函数中设置回调函数的问题
/// 通过中间层promise来实现异步函数同步化
function spiderLinks(currentUrl, body, nesting) {
    let promise = Promise.resolve();
    if (nesting === 0) {
        return promise;
    }

    const links = utilities.getPageLinks(currentUrl, body);

    links.forEach(link => {
        "use strict";
        promise = promise.then(() => spider(link, nesting - 1));
    });

    return promise;
}

function spider(url, nesting) {
    const filename = utilities.urlToFilename(url);

    return readFile(filename, "utf8")
        .then(
            body => spiderLinks(url, body, nesting),
            err => {
                "use strict";
                if (err.code !== 'ENOENT') {
                    /// 抛出错误,这个方便与在整个异步链的最后通过呢catch来捕获这个链中的错误
                    throw err;
                }
                return download(url, filename)
                    .then(body => spiderLinks(url, body, nesting));
            }
        );
}

spider(process.argv[2], 1)
    .then(() => {
        "use strict";
        console.log('Download complete');
    })
    .catch(err => {
        "use strict";
        console.log(err);
    });

能够观望上边包车型地铁代码中的函数都以从未callback的,只要求在终极catch就足以了。

在筹算api的时候,应该帮助二种格局,及扶助callback,又扶助promise

function asyncDivision(dividend, divisor, cb) {
    return new Promise((resolve, reject) => {
        "use strict";
        process.nextTick(() => {
            const result = dividend / divisor;
            if (isNaN(result) || !Number.isFinite(result)) {
                const error = new Error("Invalid operands");
                if (cb) {
                    cb(error);
                }
                return reject(error);
            }

            if (cb) {
                cb(null, result);
            }
            resolve(result);
        });
    });
}

asyncDivision(10, 2, (err, result) => {
    "use strict";
    if (err) {
        return console.log(err);
    }
    console.log(result);
});

asyncDivision(22, 11)
    .then((result) => console.log(result))
    .catch((err) => console.log(err));

 

详解Node.js中的Async和Await函数,node.jsasync

在本文中,你将学习怎么样利用Node.js中的async函数(async/await)来简化callback或Promise.

异步语言结构在任何语言中已经存在了,像c#的async/await、Kotlin的coroutines、go的goroutines,随着Node.js 8的通知,期待已久的async函数也在里头暗中同意达成了。

Node中的async函数是哪些?

当函数评释为一个Async函数它会回去三个 AsyncFunction 对象,它们看似于 Generator 因为执能够被中断。唯一的区分是它们再次来到的是 Promise 并不是 { value: any, done: Boolean } 对象。不过它们或然极度相似,你能够采纳 co 包来获得同样的功力。

在async函数中,能够等待 Promise 完结或捕获它不容的原因。

就算你要在Promise中完结部分友好的逻辑的话

function handler (req, res) {
 return request('https://user-handler-service')
 .catch((err) => {
  logger.error('Http error', err)
  error.logged = true
  throw err
 })
 .then((response) => Mongo.findOne({ user: response.body.user }))
 .catch((err) => {
  !error.logged && logger.error('Mongo error', err)
  error.logged = true
  throw err
 })
 .then((document) => executeLogic(req, res, document))
 .catch((err) => {
  !error.logged && console.error(err)
  res.status(500).send()
 })
}

能够应用 async/await 让那么些代码看起来像一道试行的代码

async function handler (req, res) {
 let response
 try {
 response = await request('https://user-handler-service') 
 } catch (err) {
 logger.error('Http error', err)
 return res.status(500).send()
 }
 let document
 try {
 document = await Mongo.findOne({ user: response.body.user })
 } catch (err) {
 logger.error('Mongo error', err)
 return res.status(500).send()
 }
 executeLogic(document, req, res)
}

在老的v8版本中,借使有有个 promise 的拒绝没有被拍卖你会取得叁个警戒,能够不要创立一个回绝错误监听函数。不过,提出在这种景况下退出你的应用程序。因为当您不管理错误时,应用程序处于叁个未知的情景。

process.on('unhandledRejection', (err) => { 
 console.error(err)
 process.exit(1)
})

async函数格局

在拍卖异步操作时,有非常多例子让她们就像是管理一同代码同样。如若接纳 Promise 或 callbacks 来消除难题时须要使用很复杂的形式也许外部库。

当需求再循环中应用异步获取数据或选拔 if-else 条件时正是一种很复杂的情景。

指数回落机制

动用 Promise 达成回落逻辑异常愚蠢

function requestWithRetry (url, retryCount) {
 if (retryCount) {
 return new Promise((resolve, reject) => {
  const timeout = Math.pow(2, retryCount)
  setTimeout(() => {
  console.log('Waiting', timeout, 'ms')
  _requestWithRetry(url, retryCount)
   .then(resolve)
   .catch(reject)
  }, timeout)
 })
 } else {
 return _requestWithRetry(url, 0)
 }
}
function _requestWithRetry (url, retryCount) {
 return request(url, retryCount)
 .catch((err) => {
  if (err.statusCode && err.statusCode >= 500) {
  console.log('Retrying', err.message, retryCount)
  return requestWithRetry(url,   retryCount)
  }
  throw err
 })
}
requestWithRetry('http://localhost:3000')
 .then((res) => {
 console.log(res)
 })
 .catch(err => {
 console.error(err)
 })

代码看的令人很发烧,你也不会想看这样的代码。大家得以采取async/await重新那一个例子,使其更简明

function wait (timeout) {
 return new Promise((resolve) => {
 setTimeout(() => {
  resolve()
 }, timeout)
 })
}

async function requestWithRetry (url) {
 const MAX_RETRIES = 10
 for (let i = 0; i <= MAX_RETRIES; i  ) {
 try {
  return await request(url)
 } catch (err) {
  const timeout = Math.pow(2, i)
  console.log('Waiting', timeout, 'ms')
  await wait(timeout)
  console.log('Retrying', err.message, i)
 }
 }
}

上面代码看起来很安适对不对

中间值

不像前边的例子那么可怕,要是你有3个异步函数依次相互信赖的气象,那么您无法不从几个难看的技术方案中举办接纳。

functionA 重临一个 Promise ,那么 functionB 需求这一个值而 functioinC 需求functionA 和 functionB 完结后的值。

方案1: then 圣诞树

function executeAsyncTask () {
 return functionA()
 .then((valueA) => {
  return functionB(valueA)
  .then((valueB) => {   
   return functionC(valueA, valueB)
  })
 })
}

用那么些技术方案,大家在第多个 then 中能够拿走 valueA 和 valueB ,然后能够向前方三个 then 同样获得 valueA 和 valueB 的值。这里不可能将圣诞树(毁掉鬼世界)拉平,假诺这么做的话会甩掉闭包, valueA 在 functioinC 上校不可用。

方案2:移动到上一流功用域

function executeAsyncTask () {
 let valueA
 return functionA()
 .then((v) => {
  valueA = v
  return functionB(valueA)
 })
 .then((valueB) => {
  return functionC(valueA, valueB)
 })
}

在那颗圣诞树中,我们选拔越来越高的成效域保变量 valueA ,因为 valueA 效用域在全体的 then 效用域外面,所以 functionC 可以获得第叁个 functionA 完毕的值。

那是三个很得力扁平化 .then 链"准确"的语法,然则,这种措施大家要求选用多个变量 valueA 和 v 来保存一样的值。

方案3:使用三个剩下的数组

function executeAsyncTask () {
 return functionA()
 .then(valueA => {
  return Promise.all([valueA, functionB(valueA)])
 })
 .then(([valueA, valueB]) => {
  return functionC(valueA, valueB)
 })
}

在函数 functionA 的 then 中选择一个数组将 valueA 和 Promise 一同再次来到,那样能使得的扁平化圣诞树(回调地狱)。

方案4:写多少个声援函数

const converge = (...promises) => (...args) => {
 let [head, ...tail] = promises
 if (tail.length) {
 return head(...args)
  .then((value) => converge(...tail)(...args.concat([value])))
 } else {
 return head(...args)
 }
}
functionA(2)
 .then((valueA) => converge(functionB, functionC)(valueA))

这么是行得通的,写二个扶植函数来隐蔽上下文变量证明。可是如此的代码非常不平价阅读,对于不熟谙这个法力的人就更难了。

运用 async/await 大家的标题奇妙般的消失

async function executeAsyncTask () {
 const valueA = await functionA()
 const valueB = await functionB(valueA)
 return function3(valueA, valueB)
}

选取 async/await 管理八个平行央求

和方面二个大约,借使您想一遍实践多个异步任务,然后在区别的地点使用它们的值能够行使 async/await 轻易消除。

async function executeParallelAsyncTasks () {
 const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
 doSomethingWith(valueA)
 doSomethingElseWith(valueB)
 doAnotherThingWith(valueC)
}

数组迭代方法

你能够在 map 、 filter 、 reduce 方法中选用async函数,固然它们看起来不是很直观,可是你能够在调整新竹尝试以下代码。

1.map

function asyncThing (value) {
 return new Promise((resolve, reject) => {
 setTimeout(() => resolve(value), 100)
 })
}

async function main () {
 return [1,2,3,4].map(async (value) => {
 const v = await asyncThing(value)
 return v * 2
 })
}

main()
 .then(v => console.log(v))
 .catch(err => console.error(err))

2.filter

function asyncThing (value) {
 return new Promise((resolve, reject) => {
 setTimeout(() => resolve(value), 100)
 })
}
async function main () {
 return [1,2,3,4].filter(async (value) => {
 const v = await asyncThing(value)
 return v % 2 === 0
 })
}
main()
 .then(v => console.log(v))
 .catch(err => console.error(err))

3.reduce

function asyncThing (value) {
 return new Promise((resolve, reject) => {
 setTimeout(() => resolve(value), 100)
 })
}
async function main () {
 return [1,2,3,4].reduce(async (acc, value) => {
 return await acc   await asyncThing(value)
 }, Promise.resolve(0))
}
main()
 .then(v => console.log(v))
 .catch(err => console.error(err))

缓慢解决方案:

[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
[ 1, 2, 3, 4 ]
10

倘即便map迭代数据你拜见到再次回到值为 [ 2, 4, 6, 8 ] ,独一的主题素材是各种值被 AsyncFunction 函数包裹在了多个 Promise 中

由此要是想要得到它们的值,供给将数组传递给 Promise.All() 来解开 Promise 的卷入。

main()
 .then(v => Promise.all(v))
 .then(v => console.log(v))
 .catch(err => console.error(err))
一开始你会等待 Promise 解决,然后使用map遍历每个值
function main () {
 return Promise.all([1,2,3,4].map((value) => asyncThing(value)))
}
main()
 .then(values => values.map((value) => value * 2))
 .then(v => console.log(v))
 .catch(err => console.error(err))

这么好像更简短一些?

假如在您的迭代器中一旦您有多少个长日子运作的一齐逻辑和另一个长日子运作的异步职分,async/await版本任然常有用

这种艺术当你能获得第二个值,就足以起来做一些划算,而毋庸等到具备 Promise 完毕才运转你的测算。尽管结果包裹在 Promise 中,可是纵然按梯次推行结果会越来越快。

关于 filter 的问题

您大概开采了,即便上边filter函数里面再次回到了 [ false, true, false, true ] , await asyncThing(value) 会重临四个 promise 那么您一定会获得一个本来的值。你能够在return以前等待全体异步达成,在开展过滤。

Reducing很简短,有点内需潜心的便是索要将最早值包裹在 Promise.resolve 中

重写基于callback的node应用成

Async 函数默许再次来到贰个 Promise ,所以你能够选取 Promises 来重写任何依据callback 的函数,然后 await 等待他们实行实现。在node中也能够应用 util.promisify 函数将依照回调的函数调换为依靠 Promise 的函数

重写基于Promise的应用程序

要更动很简短, .then 将Promise试行流串了起来。今后您能够平素动用`async/await。

function asyncTask () {
 return functionA()
  .then((valueA) => functionB(valueA))
  .then((valueB) => functionC(valueB))
  .then((valueC) => functionD(valueC))
  .catch((err) => logger.error(err))
}

转换后

async function asyncTask () {
 try {
  const valueA = await functionA()
  const valueB = await functionB(valueA)
  const valueC = await functionC(valueB)
  return await functionD(valueC)
 } catch (err) {
  logger.error(err)
 }
}
Rewriting Nod

运用 Async/Await 将相当的大程度上的使应用程序具备高可读性,裁减应用程序的拍卖复杂度(如:错误捕获),借使您也选用node v8 的本子不要紧尝试一下,或者会有新的获得。

总结

上述所述是小编给大家介绍的Node.js中的Async和Await函数,希望对大家全部协助,倘诺我们有任何疑问请给本身留言,作者会及时苏醒大家的。在此也极其多谢大家对帮客之家网址的支撑!

在本文中,你将学习怎么采用Node.js中的async函数(async/await)来简化callback或Promise. 异步语言结构在其...

鉴于比较轻松,就不做表明了。async中的队列的代码在(spider_v7.js)中,和上边我们自定义的行列很相似,也不做更加的多解释了。

1.原生JavaScript模式

本篇不对准初学者,因而会省略掉大多数的根基内容的讲授:

(spider_v1.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function spider(url, callback) {
    const filename = utilities.urlToFilename(url);
    console.log(`filename: ${filename}`);

    fs.exists(filename, exists => {
        if (!exists) {
            console.log(`Downloading ${url}`);

            request(url, (err, response, body) => {
                if (err) {
                    callback(err);
                } else {
                    mkdirp(path.dirname(filename), err => {
                        if (err) {
                            callback(err);
                        } else {
                            fs.writeFile(filename, body, err => {
                                if (err) {
                                    callback(err);
                                } else {
                                    callback(null, filename, true);
                                }
                            });
                        }
                    });
                }
            });
        } else {
            callback(null, filename, false);
        }
    });
}

spider(process.argv[2], (err, filename, downloaded) => {
    if (err) {
        console.log(err);
    } else if (downloaded) {
        console.log(`Completed the download of ${filename}`);
    } else {
        console.log(`${filename} was already downloaded`);
    }
});

下边包车型大巴代码的流水生产线大约是这么的:

  • 把url转换成filename
  • 看清该文件名是不是存在,若存在直接回到,不然走入下一步
  • 发请求,获取body
  • 把body写入到文件中

那是叁个特别简单版本的蜘蛛,他不得不抓取一个url的内容,看到上边包车型客车回调多么令人头痛。那么大家伊始张开优化。

首先,if else 这种方法得以开展优化,那几个很轻松,不用多说,放叁个比照效果:

/// before
if (err) {
    callback(err);
} else {
    callback(null, filename, true);
}

/// after
if (err) {
    return callback(err);
}
callback(null, filename, true);

代码这么写,嵌套就能够少一层,但经验丰盛的程序猿会以为,那样写过重重申了error,大家编程的要紧应该投身管理准确的数目上,在可读性上也设有这么的须求。

另多个优化是函数拆分,上面代码中的spider函数中,能够把下载文件和封存文件拆分出去。

(spider_v2.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function saveFile(filename, contents, callback) {
    mkdirp(path.dirname(filename), err => {
        if (err) {
            return callback(err);
        }
        fs.writeFile(filename, contents, callback);
    });
}

function download(url, filename, callback) {
    console.log(`Downloading ${url}`);

    request(url, (err, response, body) => {
        if (err) {
            return callback(err);
        }
        saveFile(filename, body, err => {
            if (err) {
                return callback(err);
            }
            console.log(`Downloaded and saved: ${url}`);
            callback(null, body);
        });
    })
}

function spider(url, callback) {
    const filename = utilities.urlToFilename(url);
    console.log(`filename: ${filename}`);

    fs.exists(filename, exists => {
        if (exists) {
            return callback(null, filename, false);
        }
        download(url, filename, err => {
            if (err) {
                return callback(err);
            }
            callback(null, filename, true);
        })
    });
}

spider(process.argv[2], (err, filename, downloaded) => {
    if (err) {
        console.log(err);
    } else if (downloaded) {
        console.log(`Completed the download of ${filename}`);
    } else {
        console.log(`${filename} was already downloaded`);
    }
});

上面包车型客车代码基本上是选拔原生优化后的结果,但以此蜘蛛的作用太过粗略,我们以往急需抓取有些网页中的全部url,那样才会引申出串行和互相的标题

(spider_v3.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function saveFile(filename, contents, callback) {
    mkdirp(path.dirname(filename), err => {
        if (err) {
            return callback(err);
        }
        fs.writeFile(filename, contents, callback);
    });
}

function download(url, filename, callback) {
    console.log(`Downloading ${url}`);

    request(url, (err, response, body) => {
        if (err) {
            return callback(err);
        }
        saveFile(filename, body, err => {
            if (err) {
                return callback(err);
            }
            console.log(`Downloaded and saved: ${url}`);
            callback(null, body);
        });
    })
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
    if (nesting === 0) {
        return process.nextTick(callback);
    }

    const links = utilities.getPageLinks(currentUrl, body);

    function iterate(index) {
        if (index === links.length) {
            return callback();
        }
        spider(links[index], nesting - 1, err => {
            if (err) {
                return callback(err);
            }
            iterate((index   1));
        })
    }

    iterate(0);
}

function spider(url, nesting, callback) {
    const filename = utilities.urlToFilename(url);

    fs.readFile(filename, "utf8", (err, body) => {
        if (err) {
            if (err.code !== 'ENOENT') {
                return callback(err);
            }
            return download(url, filename, (err, body) => {
                if (err) {
                    return callback(err);
                }
                spiderLinks(url, body, nesting, callback);
            });
        }

        spiderLinks(url, body, nesting, callback);
    });
}

spider(process.argv[2], 2, (err, filename, downloaded) => {
    if (err) {
        console.log(err);
    } else if (downloaded) {
        console.log(`Completed the download of ${filename}`);
    } else {
        console.log(`${filename} was already downloaded`);
    }
});

上边的代码比较此前的代码多了八个为主职能,首先是通过扶助类获取到了有个别body中的links:

const links = utilities.getPageLinks(currentUrl, body);

其间贯彻就不表达了,另贰个着力代码便是:

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
    if (nesting === 0) {
        return process.nextTick(callback);
    }

    const links = utilities.getPageLinks(currentUrl, body);

    function iterate(index) {
        if (index === links.length) {
            return callback();
        }
        spider(links[index], nesting - 1, err => {
            if (err) {
                return callback(err);
            }
            iterate((index   1));
        })
    }

    iterate(0);
}

可以说上边这一小段代码,不怕利用原生达成异步串行的pattern了。除了这一个之外,还引入了nesting的定义,通过那是那本特性,能够调控抓取档案的次序。

到这里大家就完整的兑现了串行的效果,惦念到质量,大家要付出并行抓取的作用。

(spider_v4.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");

function saveFile(filename, contents, callback) {
    mkdirp(path.dirname(filename), err => {
        if (err) {
            return callback(err);
        }
        fs.writeFile(filename, contents, callback);
    });
}

function download(url, filename, callback) {
    console.log(`Downloading ${url}`);

    request(url, (err, response, body) => {
        if (err) {
            return callback(err);
        }
        saveFile(filename, body, err => {
            if (err) {
                return callback(err);
            }
            console.log(`Downloaded and saved: ${url}`);
            callback(null, body);
        });
    })
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
    if (nesting === 0) {
        return process.nextTick(callback);
    }

    const links = utilities.getPageLinks(currentUrl, body);
    if (links.length === 0) {
        return process.nextTick(callback);
    }

    let completed = 0, hasErrors = false;

    function done(err) {
        if (err) {
            hasErrors = true;
            return callback(err);
        }

        if (  completed === links.length && !hasErrors) {
            return callback();
        }
    }

    links.forEach(link => {
        spider(link, nesting - 1, done);
    });
}

const spidering = new Map();

function spider(url, nesting, callback) {
    if (spidering.has(url)) {
        return process.nextTick(callback);
    }

    spidering.set(url, true);

    const filename = utilities.urlToFilename(url);

    /// In this pattern, there will be some issues.
    /// Possible problems to download the same url again and again。
    fs.readFile(filename, "utf8", (err, body) => {
        if (err) {
            if (err.code !== 'ENOENT') {
                return callback(err);
            }
            return download(url, filename, (err, body) => {
                if (err) {
                    return callback(err);
                }
                spiderLinks(url, body, nesting, callback);
            });
        }

        spiderLinks(url, body, nesting, callback);
    });
}

spider(process.argv[2], 2, (err, filename, downloaded) => {
    if (err) {
        console.log(err);
    } else if (downloaded) {
        console.log(`Completed the download of ${filename}`);
    } else {
        console.log(`${filename} was already downloaded`);
    }
});

这段代码同样很轻便,也可以有五个核心内容。三个是哪些完成产出:

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
    if (nesting === 0) {
        return process.nextTick(callback);
    }

    const links = utilities.getPageLinks(currentUrl, body);
    if (links.length === 0) {
        return process.nextTick(callback);
    }

    let completed = 0, hasErrors = false;

    function done(err) {
        if (err) {
            hasErrors = true;
            return callback(err);
        }

        if (  completed === links.length && !hasErrors) {
            return callback();
        }
    }

    links.forEach(link => {
        spider(link, nesting - 1, done);
    });
}

上边的代码能够说是促成产出的贰个pattern。利用循环遍历来达成。另贰个大旨是,既然是出新的,那么利用fs.exists就能存在难题,可能会再度下载同一文件,这里的减轻方案是:

  • 利用Map缓存某一url,url应该作为key

后天大家又有了新的要求,要求范围同有的时候候出现的最大数,那么在此地就引入了贰个自己以为最首要的定义:队列。

(task-Queue.js)

class TaskQueue {
    constructor(concurrency) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
    }

    pushTask(task) {
        this.queue.push(task);
        this.next();
    }

    next() {
        while (this.running < this.concurrency && this.queue.length) {
            const task = this.queue.shift();
            task(() => {
                this.running--;
                this.next();
            });
            this.running  ;
        }
    }
}

module.exports = TaskQueue;

上面的代码正是队列的达成代码,核心是next()形式,能够看来,当task加入队列中后,会登时推行,那不是说那几个职分一定马上实施,而是指的是next会即刻调用。

(spider_v5.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");
const TaskQueue = require("./task-Queue");
const downloadQueue = new TaskQueue(2);

function saveFile(filename, contents, callback) {
    mkdirp(path.dirname(filename), err => {
        if (err) {
            return callback(err);
        }
        fs.writeFile(filename, contents, callback);
    });
}

function download(url, filename, callback) {
    console.log(`Downloading ${url}`);

    request(url, (err, response, body) => {
        if (err) {
            return callback(err);
        }
        saveFile(filename, body, err => {
            if (err) {
                return callback(err);
            }
            console.log(`Downloaded and saved: ${url}`);
            callback(null, body);
        });
    })
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
    if (nesting === 0) {
        return process.nextTick(callback);
    }

    const links = utilities.getPageLinks(currentUrl, body);
    if (links.length === 0) {
        return process.nextTick(callback);
    }

    let completed = 0, hasErrors = false;

    links.forEach(link => {
        /// 给队列出传递一个任务,这个任务首先是一个函数,其次该函数接受一个参数
        /// 当调用任务时,触发该函数,然后给函数传递一个参数,告诉该函数在任务结束时干什么
        downloadQueue.pushTask(done => {
            spider(link, nesting - 1, err => {
                /// 这里表示,只要发生错误,队列就会退出
                if (err) {
                    hasErrors = true;
                    return callback(err);
                }
                if (  completed === links.length && !hasErrors) {
                    callback();
                }

                done();
            });
        });

    });
}

const spidering = new Map();

function spider(url, nesting, callback) {
    if (spidering.has(url)) {
        return process.nextTick(callback);
    }

    spidering.set(url, true);

    const filename = utilities.urlToFilename(url);

    /// In this pattern, there will be some issues.
    /// Possible problems to download the same url again and again。
    fs.readFile(filename, "utf8", (err, body) => {
        if (err) {
            if (err.code !== 'ENOENT') {
                return callback(err);
            }
            return download(url, filename, (err, body) => {
                if (err) {
                    return callback(err);
                }
                spiderLinks(url, body, nesting, callback);
            });
        }

        spiderLinks(url, body, nesting, callback);
    });
}

spider(process.argv[2], 2, (err, filename, downloaded) => {
    if (err) {
        console.log(`error: ${err}`);
    } else if (downloaded) {
        console.log(`Completed the download of ${filename}`);
    } else {
        console.log(`${filename} was already downloaded`);
    }
});

于是,为了限制并发的个数,只需在spiderLinks办法中,把task遍历放入队列就能够了。那相对来讲相当的粗略。

到此处甘休,大家使用原生JavaScript完毕了二个有相对完好意义的互联网蜘蛛,不仅可以串行,也能出现,仍是能够垄断(monopoly)并发个数。

在这里,Promise.resolve(v)静态方法只是轻巧重临一个以v为自然结果的promise,v可不传入,也得以是二个函数只怕是四个包括then艺术的目的或函数(即thenable)。

  1. 把url转换成filename
  2. 判断该公文名是还是不是留存,若存在直接重回,否则步向下一步
  3. 发请求,获取body
  4. 把body写入到文件中

总结

自家并不曾写promise和generator并发的代码。以上那一个内容来自于那本书nodejs-design-patternshttps://github.com/agelessman/MyBooks。

demo下载

function addImg(img) {
  $('#list').find('> li:last-child').html('').append(img);
};

function prepend() {
  $('<li>')
    .html('loading...')
    .appendTo($('#list'));
};

function run() {
  $('#done').hide();
  getData('map.json')
    .then(function(data) {
      $('h4').html(data.name);

      return data.list.reduce(function(promise, item) {
        return promise
          .then(prepend)
          .then(sleep(1000))
          .then(function() {
            return getImg(item.url);
          })
          .then(addImg);
      }, Promise.resolve());
    })
    .then(sleep(300))
    .then(function() {
      $('#done').show();
    });
};

$('#run').on('click', run);

(spider_v3.js)

2.使用async库

把不相同的效果与利益放到分化的函数中,会给我们带来巨大的益处,async库十分盛行,它的质量也没有错,它在这之中基于callback。

(spider_v6.js)

const request = require("request");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const utilities = require("./utilities");
const series = require("async/series");
const eachSeries = require("async/eachSeries");

function download(url, filename, callback) {
    console.log(`Downloading ${url}`);

    let body;

    series([
        callback => {
            request(url, (err, response, resBody) => {
                if (err) {
                    return callback(err);
                }
                body = resBody;
                callback();
            });
        },
        mkdirp.bind(null, path.dirname(filename)),
        callback => {
            fs.writeFile(filename, body, callback);
        }
    ], err => {
        if (err) {
            return callback(err);
        }
        console.log(`Downloaded and saved: ${url}`);
        callback(null, body);
    });
}

/// 最大的启发是实现了如何异步循环遍历数组
function spiderLinks(currentUrl, body, nesting, callback) {
    if (nesting === 0) {
        return process.nextTick(callback);
    }

    const links = utilities.getPageLinks(currentUrl, body);
    if (links.length === 0) {
        return process.nextTick(callback);
    }

    eachSeries(links, (link, cb) => {
        "use strict";
        spider(link, nesting - 1, cb);
    }, callback);
}

const spidering = new Map();

function spider(url, nesting, callback) {
    if (spidering.has(url)) {
        return process.nextTick(callback);
    }

    spidering.set(url, true);

    const filename = utilities.urlToFilename(url);

    fs.readFile(filename, "utf8", (err, body) => {
        if (err) {
            if (err.code !== 'ENOENT') {
                return callback(err);
            }
            return download(url, filename, (err, body) => {
                if (err) {
                    return callback(err);
                }
                spiderLinks(url, body, nesting, callback);
            });
        }

        spiderLinks(url, body, nesting, callback);
    });
}

spider(process.argv[2], 1, (err, filename, downloaded) => {
    if (err) {
        console.log(err);
    } else if (downloaded) {
        console.log(`Completed the download of ${filename}`);
    } else {
        console.log(`${filename} was already downloaded`);
    }
});

在上面包车型地铁代码中,大家只使用了async的多少个效果与利益:

const series = require("async/series"); // 串行
const eachSeries = require("async/eachSeries"); // 并行
const queue = require("async/queue"); // 队列

鉴于相比较轻便,就不做表达了。async中的队列的代码在(spider_v7.js)中,和上边大家自定义的队列很一般,也不做更加多解释了。

能够看出,Promise标准的内容并不算多,大家能够试着和睦达成以下Promise。

Promise是三个说道,有十分的多库达成了这几个合同,大家用的是ES6的兑现。轻巧的话promise正是叁个预定,若是做到了,就调用它的resolve方法,战败了就调用它的reject方法。它内有落到实处了then方法,then重返promise本身,这样就产生了调用链。

4.Generator

Generator很有趣,他得以让暂停函数和回复函数,利用thunkify和co那多少个库,大家上边包车型地铁代码达成起来十一分酷。

(spider_v9.js)

const thunkify = require("thunkify");
const co = require("co");
const path = require("path");
const utilities = require("./utilities");

const request = thunkify(require("request"));
const fs = require("fs");
const mkdirp = thunkify(require("mkdirp"));
const readFile = thunkify(fs.readFile);
const writeFile = thunkify(fs.writeFile);
const nextTick = thunkify(process.nextTick);

function* download(url, filename) {
    console.log(`Downloading ${url}`);

    const response = yield request(url);
    console.log(response);

    const body = response[1];
    yield mkdirp(path.dirname(filename));

    yield writeFile(filename, body);

    console.log(`Downloaded and saved ${url}`);
    return body;
}

function* spider(url, nesting) {
    const filename = utilities.urlToFilename(url);

    let body;

    try {
        body = yield readFile(filename, "utf8");
    } catch (err) {
        if (err.code !== 'ENOENT') {
            throw err;
        }
        body = yield download(url, filename);
    }

    yield  spiderLinks(url, body, nesting);
}

function* spiderLinks(currentUrl, body, nesting) {
    if (nesting === 0) {
        return nextTick();
    }

    const links = utilities.getPageLinks(currentUrl, body);

    for (let i = 0; i < links.length; i  ) {
        yield spider(links[i], nesting - 1);
    }
}

/// 通过co就自动处理了回调函数,直接返回了回调函数中的参数,把这些参数放到一个数组中,但是去掉了err信息
co(function* () {
    try {
        yield spider(process.argv[2], 1);
        console.log('Download complete');
    } catch (err) {
        console.log(err);
    }
});

说不上是then的落到实处,由于Promise要求then必需重临二个promise,所以在then调用的时候会新生成贰个promise,挂在时下promise的_next上,同多少个promise多次调用都只会回来从前生成的_next

const utilities = require("./utilities");
const request = utilities.promisify(require("request"));
const fs = require("fs");
const readFile = utilities.promisify(fs.readFile);
const writeFile = utilities.promisify(fs.writeFile);
const mkdirp = utilities.promisify(require("mkdirp"));
const path = require("path");


function saveFile(filename, contents, callback) {
  mkdirp(path.dirname(filename), err => {
    if (err) {
      return callback(err);
    }
    fs.writeFile(filename, contents, callback);
  });
}

function download(url, filename) {
  console.log(`Downloading ${url}`);

  let body;

  return request(url)
    .then(response => {
      "use strict";
      body = response.body;
      return mkdirp(path.dirname(filename));
    })
    .then(() => writeFile(filename, body))
    .then(() => {
      "use strict";
      console.log(`Downloaded adn saved: ${url}`);
      return body;
    });
}

/// promise编程的本质就是为了解决在函数中设置回调函数的问题
/// 通过中间层promise来实现异步函数同步化
function spiderLinks(currentUrl, body, nesting) {
  let promise = Promise.resolve();
  if (nesting === 0) {
    return promise;
  }

  const links = utilities.getPageLinks(currentUrl, body);

  links.forEach(link => {
    "use strict";
    promise = promise.then(() => spider(link, nesting - 1));
  });

  return promise;
}

function spider(url, nesting) {
  const filename = utilities.urlToFilename(url);

  return readFile(filename, "utf8")
    .then(
      body => spiderLinks(url, body, nesting),
      err => {
        "use strict";
        if (err.code !== 'ENOENT') {
          /// 抛出错误,这个方便与在整个异步链的最后通过呢catch来捕获这个链中的错误
          throw err;
        }
        return download(url, filename)
          .then(body => spiderLinks(url, body, nesting));
      }
    );
}

spider(process.argv[2], 1)
  .then(() => {
    "use strict";
    console.log('Download complete');
  })
  .catch(err => {
    "use strict";
    console.log(err);
  });

前言

在未曾深度应用函数回调的阅历的时候,去看这么些内容照旧有有些疑难的。由于Node.js独特的异步天性,才出现了“回调鬼世界”的标题,那篇小说中,作者比较详细的笔录了什么消除异步流难点。

作品会不短,并且那篇是对异步流情势的解释。文中会使用三个简约的网络蜘蛛的事例,它的效应是抓取钦赐UEvoqueL的网页内容并保存在类型中,在篇章的最后,能够找到整篇小说中的源码demo。

您只怕感兴趣的稿子:

  • 比方详解JavaScript中Promise的利用
  • JavaScript中的Promise使用详解
  • JavaScript Promise 用法
  • 理解JavaScript中Promise的使用
  • 详见解读JavaScript编制程序中的Promise使用
  • javascript Promise轻巧学习运用方式小结
  • Javascript中Promise的七种常用方法计算
  • 浅谈JavaScript中promise的使用
  • JavaScript中Promise的运用详解
  • 什么样从零开首利用js手写三个Promise库详解

把不相同的成效放到差异的函数中,会给我们带来巨大的补益,async库十一分风行,它的习性也不利,它个中基于callback。

这里的sleep只是为了看效果加的,可猛击查看demo!当然,Node.js的事例可查阅这里。

(spider_v6.js)

是因为then方法接受的多少个参数都以可选的,何况品种也没限制,能够是函数,也足以是二个切实可行的值,仍是能够是另贰个promise。下面是then的现实贯彻:

(task-Queue.js)

只看上边那行代码,好像看不出什么特别之处。但现实际情景况恐怕比这些纷纭许多,A要成功一件事,大概要信赖不仅仅B一个人的响应,大概需求同有的时候间向三人明白,当接过全部的答复之后再进行下一步的方案。最后翻译成代码也许像这么:

上面的代码能够说是兑现产出的一个pattern。利用循环遍历来达成。另三个中央是,既然是出现的,那么利用 fs.exists 就可以设不符合规律,或者会重新下载同一文件,这里的减轻方案是:

function sleep(ms) {
  return function(v) {
    var p = Promise();

    setTimeout(function() {
      p.resolve(v);
    }, ms);

    return p;
  };
};

function getImg(url) {
  var p = Promise();
  var img = new Image();

  img.onload = function() {
    p.resolve(this);
  };

  img.onerror = function(err) {
    p.reject(err);
  };

  img.url = url;

  return p;
};

能够见见上边的代码中的函数都以从未callback的,只须求在终极catch就足以了。

loadImg('a.jpg', function() {
  loadImg('b.jpg', function() {
    loadImg('c.jpg', function() {
      console.log('all done!');
    });
  });
});

里头贯彻就不解释了,另三个主干代码正是:

  • 一个promise也可以有两种意况:等待(pending)、已做到(fulfilled)、已拒绝(rejected)
  • 二个promise的情况只或许从“等待”转到“实现”态恐怕“拒绝”态,无法逆向调换,同期“达成”态和“拒绝”态不能够相互调换
  • promise必须达成then措施(能够说,then正是promise的主导),并且then必得回到三个promise,同一个promise的then能够调用数十次,并且回调的试行顺序跟它们被定义时的相继一致
  • then方法接受多个参数,第1个参数是打响时的回调,在promise由“等待”态调换成“完毕”态时调用,另一个是没戏时的回调,在promise由“等待”态调换来“拒绝”态时调用。同不经常候,then还行另二个promise传入,也经受二个“类then”的靶子或措施,即thenable对象。

本篇不对准初学者,由此会省略掉超过59%的底子内容的上书:

好了,上面我们来认知下Promise/A 规范:

本文由全球彩票平台发布于全球彩官网下载地址Web前端,转载请注明出处:【全球彩官网下载地址】js之异步流动调查控

TAG标签: 全球彩票平台
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。