异步流程控制(Callback 、Promise、Generator + co、async + await)

一. Callback (回调函数)

1.定义:把函数当作变量传到另一个函数里,传进去之后执行甚至返回等待之后的执行。
2.一个简单的例子

function add_callback(p1, p2 ,callback) {
var my_number = p1 + p2;
callback(my_number);
}
add_callback(5, 15, function(num){
console.log("call " + num);
});

3. error first

1.回调函数的第一个参数保留给一个错误error对象,如果有错误发生,错误将通过第一个参数err返回。
2.回调函数的第二个参数为成功响应的数据保留,如果没有错误发生,err将被设置为null, 成功的数据将从第二个参数返回。

4.callback hell

JavaScript 是由事件驱动的异步编程,一个异步的操作,我们在调用他的时候,不会马上得到结果,而是会继续执行后面的代码。这样,如果我们需要在查到结果之后才做某些事情的话,就需要把相关的代码写在回调里面,如果涉及到多个这样的异步操作,就势必会陷入到回调地狱中去。eg:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})

二. Promise(为了解决callback hell的问题)

1. Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
2. Promise A+规范:

Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,最终 ES6 中采用了 Promise/A+ 规范。Promise/A+ 官网:https://promisesaplus.com/

3.promise对象的特点

1.对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来“承诺”;
2.一旦状态改变就不会再变,任何时候都可以得到这个结果,promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果,这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。

4. 创造了一个Promise实例

var promise = new Promise( function( resolve, reject) {
/some code
if(//异步操作成功){
resolve(value);
}else{
reject(error);
}
});

5.Promise 方法:

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
1.promise.then方法返回promise的结果,then 的第一个参数是处理正确时的返回值的函数,第二个参数是处理错误时的返回的error的函数
promise.then(function(value) {
// success
}, function(error) {
// failure
});

  1. promise.catch可以捕获promise返回的错误
    var promise = new Promise(function(resolve, reject) {
    throw new Error('test');
    });
    promise.catch(function(error) {
    console.log(error);
    });
  2. Promise.resolve 是将一个值包裹成 promise对象,状态是fulfilled
    Promise.resolve('haha')
    等价于
    new Promise(function (resolve, reject) {resolve('haha')})
  3. Promise.reject 也是将一个值包裹成 promise对象,只不过状态是 rejected
    5.Promise.all()用于将多个Promise实例,包装成一个新的Promise实例。
    var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    })
    var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('hehe')
    }, 2000)
    })
    var start = Date.now()
    Promise.all([promise1, promise2])
    .then((res) => {
    console.log(Date.now() - start)
    console.log(res)
    })
    (1)只有promise1、promise2的状态都变成fulfilled,p的状态才会变成fulfilled,此时promise1、promise2的返回值组成一个数组,传递给回调函数。
    (2)只要promise1、promise2之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给新的Promise实例回调函数。
    6.Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。但是只要promise1、promise2之中有一个实例率先改变状态,新的Promise实例的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给新的Promise实例的回调函数。
    var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    })
    var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('hehe')
    }, 2000)
    })
    var start = Date.now()
    Promise.race([promise1, promise2])
    .then((res) => {
    console.log(Date.now() - start)
    console.log(res)
    })
6.扩展
  1. then 方法可以被同一个 promise 调用多次,原理在于:第一次调用之前,状态就已经从pengding->fulfilled,同时内部保留了一个值,这时多次调用.then,都会返回内部的那个值
    var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    })
    var start = Date.now()
    promise.then((res) => {
    console.log(res, Date.now() - start)
    })
    promise.then((res) => {
    console.log(res, Date.now() - start)
    })
    promise.then((res) => {
    console.log(res, Date.now() - start)
    })
  2. 在Promise构造函数里throw一个error,相当于 reject(error)
  3. promise的 then和catch都是可以链式调用的,下一个then的值是上一个promise变成fulfilled的返回值(返回值可以是promise,也可以是任意值(这个任意值内部是把它包装成promise)。如果是promise,会等待该promise返回(状态变更)),eg:
    var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    })
    var start = Date.now()
    promise
    .then((res) => {
    console.log(res, Date.now() - start)
    })
    .then((res) => {
    console.log(res, Date.now() - start)
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('hehe')
    }, 2000)
    })
    })
    .then((res) => {
    console.log(res, Date.now() - start)
    })
  4. 构造函数resolve 或 reject 只执行一次,多次调用没有任何作用
    var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    reject('error')
    })
    var start = Date.now()
    promise.then(() => {
    console.log('then', Date.now() - start)
    })
    promise.catch((e) => {
    console.error(e)
    console.error('catch', Date.now() - start)
    })
  5. 值穿透,then的参数正常情况下使用接收函数,如果传递一个非函数,则忽略,下一个then使用的是当前then的上一个返回值,也就是会跳过这个then
    var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    reject('haha')
    }, 1000)
    })
    promise2
    .catch(1)
    .catch('hehe')
    .catch((error) => {
    return console.log(1, error)
    })
    .catch((error) => {
    return console.log(2, error)
    })
  6. promise抛错(rejected),则跳过then,被最近的一个catch捕获(前提是:1.then没有第二个处理错误的函数 2.最近的catch没有值穿透),在then/catch里throw new Error(xxx)等价于return Promise.reject(xxx)
    Promise.resolve()
    .then(() => {
    // return new Error('error!!!') // 会打印1!!!!
    return Promise.reject(new Error('error!!!'))
    })
    .then((res) => {
    console.log(1, res)
    })
    .catch((e) => {
    console.error(2, e)
    })
  7. then返回的值不能是 promise 本身,否则会造成死循环,原因:then里返回的promise会等待他状态改变(或者说执行完)才会进入到下一个then
    var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    })
    var a = promise.then(() => {
    return a
    })
    a.then(console.log)
    .catch(console.error)
  8. then方法的第二个参数 vs catch方法, then里第二个处理错误的回调函数不会捕获这个then第一个处理成功时的回调函数抛出的error,then/catch里没有return值等价于return Promise.resolve(undefined)
    var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('haha')
    }, 1000)
    })
    promise
    .then(function success(res) {
    throw new Error('error')
    }, function fail1(e) {
    console.error(1, e)
    })
    .catch(function fail2(e) {
    console.error(2, e)
    })

三. Generator

1.Generator 函数是一个状态机,封装了多个内部状态;执行 Generator 函数会返回一个遍历器对象,返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
2.特点
  1. function关键字与函数名之间有一个星号
  2. 函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是“产出”)。
3.yield* 语句

由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
(1)遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
(5)如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"

4.next()方法

hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

  1. yield句本身没有返回值,或者说总是返回undefined。
  2. 每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
  3. next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
    function* f() {
    for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
    }
    }
    var g = f();
    g.next()
    // { value: 0, done: false }
    g.next()
    // { value: 1, done: false }
    g.next(true)
    // { value: 0, done: false }
5.for...of循环

for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5

6.Generator方法

Generator.prototype.throw()
Generator.prototype.return()

  1. throw()可以在函数体外抛出错误,然后在Generator函数体内捕获
    var g = function* () {
    try {
    yield;
    } catch (e) {
    console.log(e);
    }
    };
    var i = g();
    i.next();
    i.throw(new Error('出错了!'));
    // Error: 出错了!(…)
  2. return()返回给定的值,并且终结遍历Generator函数
    function* gen() {
    yield 1;
    yield 2;
    yield 3;
    }
    var g = gen();
    g.next() // { value: 1, done: false }
    g.return('foo') // { value: "foo", done: true }
    g.next() // { value: undefined, done: true }
    如果return方法调用时,不提供参数,则返回值的value属性为undefined
    function* gen() {
    yield 1;
    yield 2;
    yield 3;
    }
    var g = gen();
    g.next() // { value: 1, done: false }
    g.return() // { value: undefined, done: true }
7. co 模块
  1. co 模块是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行
  2. 用法:
    Generator 函数只要传入co函数,就会自动执行;co函数返回一个Promise对象,因此可以用then方法添加回调函数
    var co = require('co');
    var gen = function* () {
    var f1 = yield readFile('/etc/fstab');
    var f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
    co(gen).then(function (){
    console.log('Generator 函数执行完成');
    });
  3. 原理:
    自动执行机制:当异步操作有了结果,能够自动交回执行权。
    (1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
    (2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。

四. async (需要node@8以上)

  1. async 函数Generator 函数的语法糖:将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
    var fs = require('fs');
    var readFile = function (fileName) {
    return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
    if (error) reject(error);
    resolve(data);
    });
    });
    };
    var gen = function* () {
    var f1 = yield readFile('/etc/fstab');
    var f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
    写成async函数,就是下面这样。
    var asyncReadFile = async function () {
    var f1 = await readFile('/etc/fstab');
    var f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
    2.用法:
    async function getStockPriceByName(name) {
    var symbol = await getStockSymbol(name);
    var stockPrice = await getStockPrice(symbol);
    return stockPrice;
    }
    getStockPriceByName('goog').then(function (result) {
    console.log(result);
    });
    (1)async函数返回一个 Promise 对象,可以使用then方法添加回调函数
    (2)async函数内部return语句返回的值,会成为then方法回调函数的参数。
  2. await 命令
    async function f() {
    return await 123;
    }
    f().then(v => console.log(v)) // 123
    (1)wait 只能在 async 函数中使用
    (2)await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
    (3)只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
    async function f() {
    await Promise.reject('出错了');
    await Promise.resolve('hello world'); // 不会执行
    }
  3. for await...of
    for await...of循环用于遍历异步的 Iterator 接口
    async function f() {
    for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
    }
    }
    // a
    // b
  4. Generator + co vs async + await
    (1)内置执行器。
    Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
    (2)更好的语义。
    async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    (3)更广的适用性。
    co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
    (4)返回值是 Promise。
    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 一、Promise的含义 Promise在JavaScript语言中早有实现,ES6将其写进了语言标准,统一了用法...
    Alex灌汤猫阅读 818评论 0 2
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,698评论 1 56
  • 特点 Promise能将回调分离出来,在异步操作执行之后,用链式方法执行回调,虽然es5用封装函数也能实现,但是如...
    一二三kkxx阅读 616评论 0 1
  • 7月18日,7月的最后一个周末,虽然天空下着雨,却阻挡不了爱学习的小伙们学习的脚步,安徽牛商会的各个企业在...
    合肥徽马科技阅读 374评论 0 0
  • 近来因着十分劳累,又犯了眼疾,或曰干眼症,具体表现便是眼睛酸痛,总动不动便流下泪来,有如贾谊那样,所不同者,是贾长...
    竞走的蜗牛阅读 289评论 0 2