async 的本质是一个流程控制。其实在异步编程中,还有一个更为经典的模型,叫作 Promise/Deferred 模型(固然还有更多相关解决方法,好比 eventproxy,co 等,到时候遇到在挖坑) javascript
首先,咱们思考一个典型的异步编程模型,考虑这样一个题目:读取一个文件,在控制台输出这个文件内容css
var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
console.log(data);
});
复制代码
看起来很简单,再进一步: 读取两个文件,在控制台输出这两个文件内容html
var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
console.log(data);
fs.readFile('2.txt', 'utf8', function (err, data) {
console.log(data);
});
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
复制代码
要是读取更多的文件呢vue
var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
fs.readFile('2.txt', 'utf8', function (err, data) {
fs.readFile('3.txt', 'utf8', function (err, data) {
fs.readFile('4.txt', 'utf8', function (err, data) {
// ...
});
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
});
});
复制代码
这就是传说中的 callback hell,可使用 async 来改善这段代码,可是在本例中咱们要用 promise/defer 来改善它 promise 基本概念 首先它是一个对象,它和 javascript 普通的对象没什么区别,同时,它也是一种规范,跟异步操做约定了统一的接口,表示一个异步操做的最终结果,以同步的方式来写代码,执行的操做是异步的,但又保证程序执行的顺序是同步的java
promise 有一个 then 方法,then 方法能够接受 3 个函数做为参数。前两个函数对应 promise 的两种状态 fulfilled, rejected 的回调函数。第三个函数用于处理进度信息 为了理解它,一些重要原理必须记牢:.then() 老是返回一个新的 promise,以下面代码:node
var promise = readFile()
var promise2 = promise.then(readAnotherFile, console.error)
复制代码
这里 then 的参数 readAnotherFile, console.error 是表明异步操做成功后的动做 onFulfilled 或失败后的动做 OnRejected,也就是说,读取文件成功后执行 readAnotherFile 函数,不然失败打印记录错误。这种实现是两个中只有一种可能 也能够理解为:webpack
promiseSomething().then(function (fulfilled) {
// 当 promise 状态变成 fulfilled 时,调用此函数
}, function (rejected) {
// 当 promise 状态变成 rejected 时,调用此函数
}, function (progress) {
// 当返回进度信息时,调用此函数
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
复制代码
Promise 法则有两部分必须分离:web
来看一个利用 q 来处理这种问题的简单例子:面试
var Q = require('q');
var defer = Q.defer();
/**
* 获取初始 promise
* @private
*/
function getInitialPromise() {
return defer.promise;
}//欢迎加入全栈开发交流圈一块儿学习交流:864305860
/**
* 为 promise 设置三种状态的回调函数
*/
getInitialPromise().then(function (success) {
console.log(success);
}, function (error) {
console.log(error);
}, function (progress) {
console.log(progress);
});
defer.notify('in progress'); // 控制台打印 in progress
defer.resolve('resolve'); // 控制台打印 resolve
defer.reject('reject'); // 没有输出。promise 的状态只能改变一次
复制代码
promise 的传递 then 方法会返回一个 promise,在下面这个例子中,咱们用 outputPromise 指向 then 返回的 promise。数据库
var outputPromise = getInputPromise().then(function (fulfilled) {
}, function (rejected) {
});
复制代码
如今 outputPromise 就变成了受 function(fulfilled) 或者 function(rejected) 控制状态的 promise 了。直白的意思就是:当 function(fulfilled) 或者 function(rejected) 返回一个值,好比一个字符串,数组,对象等等,那么 outputPromise 的状态就会变成 fulfilled。 在下面这个例子中,咱们能够看到,当咱们把 inputPromise 的状态经过 defer.resovle() 变成 fulfilled 时,控制台输出 fulfilled. 当咱们把 inputPromise 的状态经过 defer.reject() 变成 rejected,控制台输出 rejected
var Q = require('q');
var defer = Q.defer();
/**//欢迎加入全栈开发交流圈一块儿学习交流:864305860
* 经过 defer 得到 promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当 inputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)
* 当 inputPromise 状态由未完成变成 rejected 时,调用 function(rejected)
* 将 then 返回的 promise 赋给 outputPromise
* function(fulfilled) 和 function(rejected) 经过返回字符串将 outputPromise 的状态由
* 未完成改变为 fulfilled
* @private
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
var outputPromise = getInputPromise().then(function (fulfilled) {
return 'fulfilled';
}, function (rejected) {
return 'rejected';
});
/**
* 当 outputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled),控制台打印 'fulfilled: fulfilled'。
* 当 outputPromise 状态由未完成变成 rejected, 调用 function(rejected), 控制台打印 'rejected: rejected'。
*/
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**//欢迎加入全栈开发交流圈一块儿学习交流:864305860
* 将 inputPromise 的状态由未完成变成 rejected
*/
defer.reject(); // 输出 fulfilled: rejected
/**
* 将 inputPromise 的状态由未完成变成 fulfilled
*/
//defer.resolve(); // 输出 fulfilled: fulfilled
复制代码
当 function(fulfilled) 或者 function(rejected) 抛出异常时,那么 outputPromise 的状态就会变成 rejected
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 经过 defer 得到 promise
* @private
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
function getInputPromise() {
return defer.promise;
}
/**
* 当 inputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)
* 当 inputPromise 状态由未完成变成 rejected 时,调用 function(rejected)
* 将 then 返回的 promise 赋给 outputPromise
* function(fulfilled) 和 function(rejected) 经过抛出异常将 outputPromise 的状态由
* 未完成改变为 reject
* @private
*/
var outputPromise = getInputPromise().then(function (fulfilled) {
throw new Error('fulfilled');
}, function (rejected) {
throw new Error('rejected');
});
/**
* 当 outputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)。
* 当 outputPromise 状态由未完成变成 rejected, 调用 function(rejected)。
*/
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**
* 将 inputPromise 的状态由未完成变成 rejected
*/
defer.reject(); // 控制台打印 rejected [Error:rejected]
//欢迎加入全栈开发交流圈一块儿学习交流:864305860
/**
* 将 inputPromise 的状态由未完成变成 fulfilled
*/
//defer.resolve(); // 控制台打印 rejected [Error:fulfilled]
复制代码
当 function(fulfilled) 或者 function(rejected) 返回一个 promise 时,outputPromise 就会成为这个新的 promise. 这样作的意义在于聚合结果 (Q.all),管理延时,异常恢复等等 好比说咱们想要读取一个文件的内容,而后把这些内容打印出来。可能会写出这样的代码:
// 错误的写法
var outputPromise = getInputPromise().then(function (fulfilled) {
fs.readFile('test.txt', 'utf8', function (err, data) {
return data;
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
});
复制代码
然而这样写是错误的,由于 function(fulfilled) 并无返回任何值。须要下面的方式:
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 经过 defer 得到promise
* @private
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
function getInputPromise() {
return defer.promise;
}
/**
* 当 inputPromise 状态由未完成变成 fulfil时,调用 function(fulfilled)
* 当 inputPromise 状态由未完成变成 rejected时,调用 function(rejected)
* 将 then 返回的 promise 赋给 outputPromise
* function(fulfilled) 将新的 promise 赋给 outputPromise
* 未完成改变为 reject
* @private
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
var outputPromise = getInputPromise().then(function (fulfilled) {
var myDefer = Q.defer();
fs.readFile('test.txt', 'utf8', function (err, data) {
if (!err && data) {
myDefer.resolve(data);
}
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
return myDefer.promise;
}, function (rejected) {
throw new Error('rejected');
});
/**
* 当 outputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled),控制台打印 test.txt 文件内容。
*
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
outputPromise.then(function (fulfilled) {
console.log(fulfilled);
}, function (rejected) {
console.log(rejected);
});
/**
* 将 inputPromise 的状态由未完成变成 rejected
*/
//defer.reject();
/**
* 将 inputPromise 的状态由未完成变成 fulfilled
*/
defer.resolve(); // 控制台打印出 test.txt 的内容
复制代码
方法传递 方法传递有些相似于 Java 中的 try 和 catch。当一个异常没有响应的捕获时,这个异常会接着往下传递 方法传递的含义是当一个状态没有响应的回调函数,就会沿着 then 往下找 没有提供 function(rejected)
var outputPromise = getInputPromise().then(function (fulfilled) { })
复制代码
若是 inputPromise 的状态由未完成变成 rejected, 此时对 rejected 的处理会由 outputPromise 来完成
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 经过defer得到promise
* @private
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
function getInputPromise() {
return defer.promise;
}
/**
* 当 inputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)
* 当 inputPromise 状态由未完成变成 rejected 时,这个 rejected 会传向 outputPromise
*/
var outputPromise = getInputPromise().then(function (fulfilled) {
return 'fulfilled'
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**
* 将 inputPromise 的状态由未完成变成 rejected
*/
defer.reject('inputpromise rejected'); // 控制台打印 rejected: inputpromise rejected
/**
* 将 inputPromise的状态由未完成变成fulfilled
*///欢迎加入全栈开发交流圈一块儿学习交流:864305860
//defer.resolve();
复制代码
没有提供 function(fulfilled)
var outputPromise = getInputPromise().then(null, function (rejected) { })
复制代码
若是 inputPromise 的状态由未完成变成 fulfilled, 此时对 fulfil 的处理会由 outputPromise 来完成
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
* 经过defer得到promise
* @private
*/
function getInputPromise() {
return defer.promise;
}//欢迎加入全栈开发交流圈一块儿学习交流:864305860
/**
* 当 inputPromise 状态由未完成变成 fulfil时,传递给 outputPromise
* 当 inputPromise 状态由未完成变成 rejected时,调用 function(rejected)
* function(fulfilled) 将新的 promise 赋给 outputPromise
* 未完成改变为 reject
* @private
*/
var outputPromise = getInputPromise().then(null, function (rejected) {
return 'rejected';
}); //欢迎加入全栈开发交流圈一块儿学习交流:864305860
outputPromise.then(function (fulfilled) {
console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
console.log('rejected: ' + rejected);
});
/**
* 将 inputPromise 的状态由未完成变成 rejected
*/
// defer.reject('inputpromise rejected');
/**
* 将 inputPromise 的状态由未完成变成fulfilled
*/
defer.resolve('inputpromise fulfilled'); // 控制台打印fulfilled: inputpromise fulfilled
复制代码
可使用 fail(function(error)) 来专门针对错误处理,而不是使用 then(null,function(error))
var outputPromise = getInputPromise().fail(function (error) { })
复制代码
看这个例子:
var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**//欢迎加入全栈开发交流圈一块儿学习交流:864305860
* 经过defer得到promise
* @private
*/
function getInputPromise() {
return defer.promise;
}
/**
* 当 inputPromise 状态由未完成变成 fulfil 时,调用 then(function(fulfilled))
* 当 inputPromise 状态由未完成变成 rejected 时,调用 fail(function(error))
* function(fulfilled) 将新的 promise 赋给 outputPromise
* 未完成改变为reject
* @private
*/
var outputPromise = getInputPromise().then(function (fulfilled) {
return fulfilled;
}).fail(function (error) {
console.log('fail: ' + error);
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
/**
* 将 inputPromise 的状态由未完成变成 rejected
*/
defer.reject('inputpromise rejected');// 控制台打印 fail: inputpromise rejected
/**
* 将 inputPromise 的状态由未完成变成 fulfilled
*/
//defer.resolve('inputpromise fulfilled');
复制代码
可使用 progress(function (progress)) 来专门针对进度信息进行处理,而不是使用 then(function (success) { }, function (error) { }, function (progress) { })
var Q = require('q');
var defer = Q.defer();
/**
* 获取初始 promise
* @private
*/
function getInitialPromise() {
return defer.promise;
}
/**//欢迎加入全栈开发交流圈一块儿学习交流:864305860
* 为 promise 设置 progress 信息处理函数
*/
var outputPromise = getInitialPromise().then(function (success) {
}).progress(function (progress) {
console.log(progress);
});
defer.notify(1);
defer.notify(2); // 控制台打印 1,2
复制代码
promise 链 promise 链提供了一种让函数顺序执行的方法 函数顺序执行是很重要的一个功能。好比知道用户名,须要根据用户名从数据库中找到相应的用户,而后将用户信息传给下一个函数进行处理
var Q = require('q');
var defer = Q.defer();
// 一个模拟数据库
var users = [{ 'name': 'andrew', 'passwd': 'password' }];
function getUsername() {
return defer.promise;
}
function getUser(username) {
var user;
users.forEach(function (element) {
if (element.name === username) {
user = element;
}
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
return user;
}
// promise 链
getUsername().then(function (username) {
return getUser(username);
}).then(function (user) {
console.log(user);
});
defer.resolve('andrew');
复制代码
咱们经过两个 then 达到让函数顺序执行的目的。 then 的数量实际上是没有限制的。固然,then 的数量过多,要手动把他们连接起来是很麻烦的。好比
foo(initialVal).then(bar).then(baz).then(qux)
复制代码
这时咱们须要用代码来动态制造 promise 链
var funcs = [foo, bar, baz, qux]
var result = Q(initialVal)
funcs.forEach(function (func) {
result = result.then(func)
})
return result
复制代码
固然,咱们能够再简洁一点
var funcs = [foo, bar, baz, qux]
funcs.reduce(function (pre, current),Q(initialVal){
return pre.then(current)
})//欢迎加入全栈开发交流圈一块儿学习交流:864305860
复制代码
看一个具体的例子
function foo(result) {
console.log(result);
return result + result;
}
// 手动连接
Q('hello').then(foo).then(foo).then(foo);
// 控制台输出: hello
// hellohello
// hellohellohello
// 动态连接
var funcs = [foo, foo, foo];
var result = Q('hello');
funcs.forEach(function (func) {
result = result.then(func);
});
//欢迎加入全栈开发交流圈一块儿学习交流:864305860
// 精简后的动态连接
funcs.reduce(function (prev, current) {
return prev.then(current);
}, Q('hello'));
复制代码
对于 promise 链,最重要的是须要理解为何这个链可以顺序执行。若是可以理解这点,那么之后本身写 promise 链能够说是轻车熟路啊 promise 组合 回到咱们一开始读取文件内容的例子。若是如今让咱们把它改写成 promise 链,是否是很简单呢?
var Q = require('q'),
fs = require('fs');
function printFileContent(fileName) {
return function () {
var defer = Q.defer();
fs.readFile(fileName, 'utf8', function (err, data) {
if (!err && data) {
console.log(data);
defer.resolve();
}
})
return defer.promise;
}
} //欢迎加入全栈开发交流圈一块儿学习交流:864305860
// 手动连接
printFileContent('sample01.txt')()
.then(printFileContent('sample02.txt'))
.then(printFileContent('sample03.txt'))
.then(printFileContent('sample04.txt')); // 控制台顺序打印 sample01 到 sample04 的内容
复制代码
咱们会发现为何要他们顺序执行呢,若是他们可以并行执行不是更好吗? 咱们只须要在他们都执行完成以后,获得他们的执行结果就能够了 咱们能够经过 Q.all([promise1,promise2...]) 将多个 promise 组合成一个 promise 返回。 注意:
咱们来把上面读取文件内容的例子改为并行执行吧
var Q = require('q');
var fs = require('fs');
/**//欢迎加入全栈开发交流圈一块儿学习交流:864305860
*读取文件内容
*@private
*/
function printFileContent(fileName) {
// Todo: 这段代码不够简洁。可使用 Q.denodeify 来简化
var defer = Q.defer();
fs.readFile(fileName, 'utf8', function (err, data) {
if (!err && data) {
console.log(data);
defer.resolve(fileName + ' success ');
} else {
defer.reject(fileName + ' fail ');
}
}) //欢迎加入全栈开发交流圈一块儿学习交流:864305860
return defer.promise;
}
Q.all([printFileContent('sample01.txt'), printFileContent('sample02.txt'), printFileContent('sample03.txt'), printFileContent('sample04.txt')])
.then(function (success) {
console.log(success);
}); // 控制台打印各个文件内容 顺序不必定
复制代码
如今知道 Q.all 会在任意一个 promise 进入 reject 状态后当即进入 reject 状态。若是咱们须要等到全部的 promise 都发生状态后(有的 fulfil, 有的 reject),再转换 Q.all 的状态, 这时咱们可使用 Q.allSettled
var Q = require('q'),
fs = require('fs');
/**
*读取文件内容
*@private
*/
function printFileContent(fileName) {
// Todo: 这段代码不够简洁。可使用Q.denodeify来简化
var defer = Q.defer();
fs.readFile(fileName, 'utf8', function (err, data) {
if (!err && data) {
console.log(data);
defer.resolve(fileName + ' success ');
} else {
defer.reject(fileName + ' fail ');
}//欢迎加入全栈开发交流圈一块儿学习交流:864305860
})
return defer.promise;
}
Q.allSettled([printFileContent('nosuchfile.txt'),
printFileContent('sample02.txt'),
printFileContent('sample03.txt'),
printFileContent('sample04.txt')])
.then(function (results) {
results.forEach(
function (result) {
console.log(result.state);
}
);
});//欢迎加入全栈开发交流圈一块儿学习交流:864305860
复制代码
结束 promise 链 一般,对于一个 promise 链,有两种结束的方式。第一种方式是返回最后一个 promise 如 return foo().then(bar); 第二种方式就是经过 done 来结束 promise 链 如 foo().then(bar).done() 为何须要经过 done 来结束一个 promise 链呢? 若是在咱们的链中有错误没有被处理,那么在一个正确结束的 promise 链中,这个没被处理的错误会经过异常抛出
var Q = require('q');
function getPromise(msg, timeout, opt) {
var defer = Q.defer();
setTimeout(function () {
console.log(msg);
if (opt)
defer.reject(msg);
else
defer.resolve(msg);
}, timeout);
return defer.promise;
} //欢迎加入全栈开发交流圈一块儿学习交流:864305860
/**
* 没有用 done() 结束的 promise 链
* 因为 getPromse('2',2000,'opt') 返回 rejected, getPromise('3',1000) 就没有执行
* 而后这个异常并无任何提醒,是一个潜在的 bug
*/
getPromise('1', 3000)
.then(function () { return getPromise('2', 2000, 'opt') })
.then(function () { return getPromise('3', 1000) });
/**
* 用 done() 结束的 promise 链
* 有异常抛出
*/
getPromise('1', 3000)
.then(function () { return getPromise('2', 2000, 'opt') })
.then(function () { return getPromise('3', 1000) })
.done();
复制代码
promise 表明一个异步操做的最终结果。主要经过 promise 的 then 方法订阅其最终结果的处理回调函数,和订阅因某缘由没法成功获取最终结果的处理回调函数。
A 与 A+ 的不一样点
结语
感谢您的观看,若有不足之处,欢迎批评指正。
本次给你们推荐一个免费的学习群,里面归纳移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。 对web开发技术感兴趣的同窗,欢迎加入Q群:864305860,无论你是小白仍是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时天天更新视频资料。 最后,祝你们早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。