一. 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
});
- promise.catch可以捕获promise返回的错误
var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
- Promise.resolve 是将一个值包裹成 promise对象,状态是fulfilled
Promise.resolve('haha')
等价于
new Promise(function (resolve, reject) {resolve('haha')})
- 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.扩展
- 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)
})
- 在Promise构造函数里throw一个error,相当于 reject(error)
- 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)
})
- 构造函数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)
})
- 值穿透,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)
})
- 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)
})
- 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)
- 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.特点
- function关键字与函数名之间有一个星号
- 函数体内部使用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 }
- yield句本身没有返回值,或者说总是返回undefined。
- 每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
- 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()
- throw()可以在函数体外抛出错误,然后在Generator函数体内捕获
var g = function* () {
try {
yield;
} catch (e) {
console.log(e);
}
};
var i = g();
i.next();
i.throw(new Error('出错了!'));
// Error: 出错了!(…)
- 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 模块
- co 模块是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行
- 用法:
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 函数执行完成');
}); - 原理:
自动执行机制:当异步操作有了结果,能够自动交回执行权。
(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
(2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。
四. async (需要node@8以上)
- 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方法回调函数的参数。 - 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'); // 不会执行
}
- for await...of
for await...of循环用于遍历异步的 Iterator 接口
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b
- 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方法指定下一步的操作。