/**
- @description 模仿信号机制
- // function A
- const demoSignal = createSignal(1000); // currentTime: 1552641398505
- await doSomething();
- await doSomethingElse();
- demoSignal.fulfill('hahah'); // currentTime: 1552641413819
- // function B
- const res = await demoSignal; // currentTime: 1552641398505
- console.log(res, Date.now()); // print 'haha, 1552641413819'
- @param {Number} timeout 超时时间,超过时间自动resolve
- @return {Signal} 信号 extends Promise fulfill
*/
exports.createSignal = function(timeout = 4000) {
let resolver;
const signal = new Promise(resolve => {
resolver = resolve;
});
signal.fulfill = resolver;
if (timeout) {
setTimeout(() => signal.fulfill(), timeout);
}
return signal;
};
使用
1、创建信号量promise ctx.dingqiMarketQiangGouSignal = createSignal();
2、其他地方需要等待这个信号量 就 const qiangGouDelData = ctx.dingqiMarketQiangGouSignal ? await ctx.dingqiMarketQiangGouSignal : [];
3、最后原始信号量执行resolve ctx.dingqiMarketQiangGouSignal.fulfill(fulfillArray); 原等待的地方 获得数据
总结:
promise 遇到await 的时候一定要等到 resolve 执行之后才会执行下面的逻辑;
例如
setTimeout( function () {
console.log( 'setTimeout' )
}, 0 );
let a;
const b = new Promise( function ( resolve ) {
console.log( 'promise1' )
a = resolve;
} ).then( function () {
console.log( 'promise2' )
} );
async function foo() {
console.log( '001' )
await b;
console.log( '002' )
}
foo();
a(); // 只有执行了 这行 002 才能打印
console.log( 'script end' );
[Log] promise1
[Log] 001
[Log] script end
[Log] promise2
[Log] 002
< undefined
[Log] setTimeout
setTimeout( function () {
console.log( 'setTimeout' )
}, 0 );
new Promise( function ( resolve ) {
console.log( 'promise1' )
resolve();
} ).then( function () {
console.log( 'promise2' )
} )
console.log( 'script end' )
[Log] promise1
[Log] script end
[Log] promise2
< undefined 打印的原因?
[Log] setTimeout
Promise 使用,Promise定义后会立马执行
async function foo() {
console.log( 'foo start' )
await bar()
console.log( 'foo end' )
}
async function bar() {
console.log( 'bar' )
}
console.log( 'script start' )
setTimeout( function () {
console.log( 'setTimeout' )
}, 0 )
foo();
const a = new Promise( function ( resolve ) {
console.log( 'promise1' )
return resolve('222');
} );
a.then( function (v) {
console.log( 'promise2'+v )
} );
console.log( 'script end' )
[Log] script start
[Log] foo start
[Log] bar
[Log] promise1
[Log] script end
[Log] promise2222
[Log] foo end
< undefined
[Log] setTimeout
解析:
对于await来说,分2个情况
不是promise对象
是promise对象
如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果;
如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
我们要先明确一些基本概念,在Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。
宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任务:process.nextTick, Promise, Object.observer, MutationObserver.
下面来根据线程说下具体的执行顺序,我们都知道js是单线程的,他在执行的时候把各种任务放在队列里,会依此执行,但是根据以上的信息,我们先把任务流模拟下:
首先是2个函数声明,虽然有async关键字,但不是调用我们就不看。然后首先是打印同步代码 console.log('script start');//这是全局script任务,作为宏任务1
然后将将setTimeout放入宏任务队列,这里是宏任务2
调用foo,打印 同步代码 console.log( foo start' );
接着 await async2(),我们来分析下它做了什么:
先得到await右侧表达式的结果。执行async2(),打印同步代码console.log('bar'), 参考上面的结论,这时候是进程被阻塞的,被阻塞后,要执行async之外的代码的,输出promise1,??这里只是把promise2推入微任务队列,并没有执行。微任务会在当前宏任务的同步代码执行完毕,才会依次执行,然后foo end,最后return Promise.resolve(undefined);
-
对于 await Promise.resolve(undefined) 如何理解呢?
根据 MDN 原话我们知道
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
在我们这个例子中,就是Promise.resolve(undefined)正常处理完成,并返回其处理结果。那么await bar()就算是执行结束了。
目前这个promise的状态是fulfilled,等其处理结果返回就可以执行await下面的代码了。
那何时能拿到处理结果呢?
回忆平时我们用promise,调用resolve后,何时能拿到处理结果?是不是需要在then的第一个参数里,才能拿到结果。(调用resolve时,会把then的参数推入微任务队列,等主线程空闲时,再调用它)
所以这里的 await Promise.resolve() 就类似于:
把then的第一个回调参数 (undefined) => {} 推入微任务队列。
then执行完,才是await far()执行结束。
await far()执行结束,才能继续执行后面的代码.
此时当前宏任务1都执行完了,要处理微任务队列里的代码。
微任务队列,先进先出的原则,
执行微任务1,打印promise2
执行微任务2,没什么内容..
但是微任务2执行后,await far()语句结束,后面的代码不再被阻塞,所以打印
console.log( 'foo end' )
等到宏任务1执行完,还有它队列里的为任务也执行完毕,就开始宏任务2,打印结果:setTimeout
;(function (root) {
/**
- InterceptorManage, 代表一个拦截器,用于发起jsonp前及响应之后的统一操作
- @constructor
- @param {array} handlers - 存放数组
/
class InterceptorManager {
constructor () {
this.handlers = []
}
/*- 添加promise(resolve, reject)对象至数组
- @param {function} fulfilled - promise resolve函数
- @param {function} rejected - promise rejected函数
- @return {number} id - 在handlers数组中的位置
/
use (fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
})
return this.handlers.length - 1
}
/* - 通过id删除handlers中的项
- @param {number} id - use函数返回的所在handlers的下标
*/
eject (id) {
if (this.handlers[id]) {
this.handlers[id] = null
}
}
}
jsonp.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
/** 解析器parser
- parser.toJSON() 将对象转换成字符串
- parser.toObject() 将键值对字符串转换成数组
/
let parser = {
/*- 将对象转换成特定字符串格式
- @param {object} obj - 目标对象
- @param {string} separator - 键值对之中的分隔符,默认为'='
- @param {string} delimiter - 键值对连接的分隔符,默认为'&'
- @example
- // return a=1&b=2
- parser.toJSON({a:1, b: 2})
- // return a=1?b=2
- parser.toJSON({a:1, b: 2}, undefined, '?')
/
toJSON (obj = {}, separator = '=', delimiter = '&') {
let result = ''
Object.entries(obj).forEach(([key, value], index) => {
result += ((index === 0) ? '' : delimiter) + key + separator + value
})
return result
},
/* - 将键值对字符串转换成对象
- @param {string} jsonstr - 要转换的键值对字符串
- @param {*} separator - 键值对之中的分隔符,默认为'='
- @param {*} delimiter - 键值对连接的分隔符,默认为'&'
- @example
- // return {a: '1', b: '2'}
- parser.toObject('a=1&b=2')
- // return {a: '1', b: '2'}
- parser.toObject('a:1?b:2', ':', '?')
*/
toObject (jsonstr, separator = '=', delimiter = '&') {
if (typeof jsonstr !== 'string') {
throw new Error('first parameter must be a string')
}
return jsonstr.split(delimiter).reduce((result, item) => {
result[item.split(separator)[0]] = item.split(separator)[1]
return result
}, {})
}
}
/** 定义空函数 */
let noop = () => {}
/**
- promise 实现的jsonp函数
- @param {string} url - jsonp 请求地址
- @param {object} options - jsonp 请求参数对象,其中属性jsonpCallback 为函数名
/
function jsonp (url, options = {jsonpCallback: 'callback'}) {
// 用法 Promise.resolve https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
let promise = Promise.resolve(options)
let dispatchRequest = function () {
/* 返回promise, 内部为传统jsonp的用法及代码 */
return new Promise((resolve, reject) => {
let s = document.createElement('script')
let index = url.indexOf('?')
if (index > -1) {
Object.assign(options, parser.toObject(url.substring(index + 1)))
url = url.substring(0, index)
}
let cName = 'jsonp' + Date.now()
options[options['jsonpCallback']] = cName
delete options['jsonpCallback']
window[cName] = function (res) {
resolve(res)
}
s.src = url + '?' + parser.toJSON(options)
document.body.appendChild(s)
s.remove()
s.onerror = function (err) {
rejected(err)
}
})
}
let chain = [dispatchRequest, noop]
// request dispatch前置处理
jsonp.interceptors.request.handlers.forEach((interceptor) => {
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// response dispatch数据之后处理
jsonp.interceptors.response.handlers.forEach((interceptor) => {
chain.push(interceptor.fulfilled, interceptor.rejected)
})
while (chain.length) {
// 循环处理 里面的所有逻辑
promise = promise.then(chain.shift(), chain.shift())
}
return promise
// 第一种调用
// jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', {
// wd: 'github',
// jsonpCallback: 'cb'
// }).then((res) => {
// console.log(res)
// })
// 第二种调用
//Add a request interceptor
// jsonp.interceptors.request.use((config) => {
// // Do something before request is send
// return config
// }, (error) => {
// // Do something with request error
// return Promise.reject(error)
// })
// //Add a response interceptor
// jsonp.interceptors.response.use((response) => {
// // Do something with response data
// return response
// }, (error) => {
// // Do something with response error
// return Promise.reject(error)
// })
// 示例
// var thenable = { then: function(resolve, reject) {
// reject('444');
// // throw new TypeError("Throwing");
// // resolve("Resolving");
// }};
// var p2 = Promise.resolve(thenable);
// p2.then(function(v) {
// // 不会被调用
// }, function(e) {
// console.log(e); // TypeError: Throwing
// });
}
/** ??榛庾?*/
if (typeof define !== 'undefined' && define.amd) {
define([], function () {
return jsonp
})
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = jsonp
} else {
root.jsonp = jsonp
}
})(this)